mirror of
https://github.com/c-cube/nanoev.git
synced 2025-12-05 19:00:35 -05:00
initial commit
This commit is contained in:
commit
bb84d9d685
10 changed files with 382 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
_build
|
||||
_opam
|
||||
*.tmp
|
||||
15
.ocamlformat
Normal file
15
.ocamlformat
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
version = 0.26.2
|
||||
profile=conventional
|
||||
margin=80
|
||||
if-then-else=k-r
|
||||
parens-ite=true
|
||||
parens-tuple=multi-line-only
|
||||
sequence-style=terminator
|
||||
type-decl=sparse
|
||||
break-cases=toplevel
|
||||
cases-exp-indent=2
|
||||
field-space=tight-decl
|
||||
leading-nested-match-parens=true
|
||||
module-item-spacing=compact
|
||||
quiet=true
|
||||
ocaml-version=4.08.0
|
||||
26
Makefile
Normal file
26
Makefile
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
DUNE_OPTS?=
|
||||
build:
|
||||
dune build @install $(DUNE_OPTS)
|
||||
|
||||
clean:
|
||||
@dune clean
|
||||
|
||||
test:
|
||||
@dune runtest $(DUNE_OPTS)
|
||||
|
||||
test-autopromote:
|
||||
@dune runtest $(DUNE_OPTS) --auto-promote
|
||||
|
||||
doc:
|
||||
@dune build $(DUNE_OPTS) @doc
|
||||
|
||||
format:
|
||||
@dune build $(DUNE_OPTS) @fmt --auto-promote
|
||||
|
||||
format-check:
|
||||
@dune build $(DUNE_OPTS) @fmt --display=quiet
|
||||
|
||||
WATCH?= @check @runtest
|
||||
watch:
|
||||
dune build $(DUNE_OPTS) -w $(WATCH)
|
||||
23
dune-project
Normal file
23
dune-project
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
(lang dune 2.7)
|
||||
|
||||
(name nanoev)
|
||||
(generate_opam_files true)
|
||||
|
||||
(source
|
||||
(github c-cube/nanoev))
|
||||
|
||||
(authors "Simon Cruanes")
|
||||
|
||||
(maintainers "Simon Cruanes")
|
||||
|
||||
(license MIT)
|
||||
|
||||
;(documentation https://url/to/documentation)
|
||||
|
||||
(package
|
||||
(name nanoev)
|
||||
(synopsis "Tiny event loop around `select`")
|
||||
(depends ocaml dune base-unix)
|
||||
(tags (unix select async)))
|
||||
|
||||
; See the complete stanza docs at https://dune.readthedocs.io/en/stable/reference/dune-project/index.html
|
||||
30
nanoev.opam
Normal file
30
nanoev.opam
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# This file is generated by dune, edit dune-project instead
|
||||
opam-version: "2.0"
|
||||
synopsis: "Tiny event loop around `select`"
|
||||
maintainer: ["Simon Cruanes"]
|
||||
authors: ["Simon Cruanes"]
|
||||
license: "MIT"
|
||||
tags: ["unix" "select" "async"]
|
||||
homepage: "https://github.com/c-cube/nanoev"
|
||||
bug-reports: "https://github.com/c-cube/nanoev/issues"
|
||||
depends: [
|
||||
"ocaml"
|
||||
"dune" {>= "2.7"}
|
||||
"base-unix"
|
||||
"odoc" {with-doc}
|
||||
]
|
||||
build: [
|
||||
["dune" "subst"] {dev}
|
||||
[
|
||||
"dune"
|
||||
"build"
|
||||
"-p"
|
||||
name
|
||||
"-j"
|
||||
jobs
|
||||
"@install"
|
||||
"@runtest" {with-test}
|
||||
"@doc" {with-doc}
|
||||
]
|
||||
]
|
||||
dev-repo: "git+https://github.com/c-cube/nanoev.git"
|
||||
5
src/dune
Normal file
5
src/dune
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
(library
|
||||
(name nanoev)
|
||||
(public_name nanoev)
|
||||
(synopsis "Nano ev loop")
|
||||
(libraries unix))
|
||||
61
src/heap.ml
Normal file
61
src/heap.ml
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
type 'a tree =
|
||||
| E
|
||||
| N of int * 'a * 'a tree * 'a tree
|
||||
|
||||
type 'a t = {
|
||||
leq: 'a -> 'a -> bool;
|
||||
mutable t: 'a tree;
|
||||
}
|
||||
|
||||
let create ~leq () : _ t = { leq; t = E }
|
||||
|
||||
let[@inline] is_empty (self : _ t) =
|
||||
match self.t with
|
||||
| E -> true
|
||||
| N _ -> false
|
||||
|
||||
exception Empty
|
||||
|
||||
open struct
|
||||
(** Rank of the tree *)
|
||||
let[@inline] rank_ = function
|
||||
| E -> 0
|
||||
| N (r, _, _, _) -> r
|
||||
|
||||
(** Make a balanced node labelled with [x], and subtrees [a] and [b].
|
||||
We ensure that the right child's rank is ≤ to the rank of the
|
||||
left child (leftist property). The rank of the resulting node
|
||||
is the length of the rightmost path. *)
|
||||
let[@inline] mk_node_ x a b =
|
||||
if rank_ a >= rank_ b then
|
||||
N (rank_ b + 1, x, a, b)
|
||||
else
|
||||
N (rank_ a + 1, x, b, a)
|
||||
|
||||
let rec merge ~leq t1 t2 =
|
||||
match t1, t2 with
|
||||
| t, E -> t
|
||||
| E, t -> t
|
||||
| N (_, x, a1, b1), N (_, y, a2, b2) ->
|
||||
if leq x y then
|
||||
mk_node_ x a1 (merge ~leq b1 t2)
|
||||
else
|
||||
mk_node_ y a2 (merge ~leq t1 b2)
|
||||
end
|
||||
|
||||
let clear self = self.t <- E
|
||||
|
||||
let[@inline] insert (self : _ t) x : unit =
|
||||
self.t <- merge ~leq:self.leq self.t (N (1, x, E, E))
|
||||
|
||||
let[@inline] peek_min_exn (self : _ t) =
|
||||
match self.t with
|
||||
| E -> raise Empty
|
||||
| N (_, x, _, _) -> x
|
||||
|
||||
let[@inline] pop_min_exn (self : _ t) =
|
||||
match self.t with
|
||||
| E -> raise Empty
|
||||
| N (_, x, l, r) ->
|
||||
self.t <- merge ~leq:self.leq l r;
|
||||
x
|
||||
13
src/heap.mli
Normal file
13
src/heap.mli
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
type 'a t
|
||||
|
||||
val create : leq:('a -> 'a -> bool) -> unit -> 'a t
|
||||
|
||||
val is_empty : _ t -> bool
|
||||
(** [is_empty h] returns [true] if the heap [h] is empty. *)
|
||||
|
||||
exception Empty
|
||||
|
||||
val clear : _ t -> unit
|
||||
val insert : 'a t -> 'a -> unit
|
||||
val peek_min_exn : 'a t -> 'a
|
||||
val pop_min_exn : 'a t -> 'a
|
||||
182
src/nanoev.ml
Normal file
182
src/nanoev.ml
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
(* module type BACKEND = Intf.BACKEND *)
|
||||
|
||||
let ( let@ ) = ( @@ )
|
||||
let now_ : unit -> float = Unix.gettimeofday
|
||||
|
||||
(** Callback list *)
|
||||
type cbs =
|
||||
| Nil
|
||||
| Sub : 'a * ('a -> unit) * cbs -> cbs
|
||||
|
||||
let[@inline] cb_is_empty = function
|
||||
| Nil -> true
|
||||
| Sub _ -> false
|
||||
|
||||
type timer_ev =
|
||||
| Timer : {
|
||||
deadline: float;
|
||||
x: 'a;
|
||||
f: 'a -> unit;
|
||||
}
|
||||
-> timer_ev
|
||||
|
||||
type per_fd = {
|
||||
fd: Unix.file_descr;
|
||||
mutable r: cbs;
|
||||
mutable w: cbs;
|
||||
}
|
||||
|
||||
type t = {
|
||||
timer: timer_ev Heap.t;
|
||||
fds: (Unix.file_descr, per_fd) Hashtbl.t;
|
||||
mutable sub_r: Unix.file_descr list;
|
||||
mutable sub_w: Unix.file_descr list;
|
||||
mutable sub_up_to_date: bool;
|
||||
(** are [sub_r] and [sub_w] faithful reflections of [fds]? *)
|
||||
lock: Mutex.t;
|
||||
}
|
||||
|
||||
let leq_timer (Timer a) (Timer b) = a.deadline <= b.deadline
|
||||
|
||||
let create () : t =
|
||||
{
|
||||
timer = Heap.create ~leq:leq_timer ();
|
||||
fds = Hashtbl.create 16;
|
||||
sub_r = [];
|
||||
sub_w = [];
|
||||
sub_up_to_date = true;
|
||||
lock = Mutex.create ();
|
||||
}
|
||||
|
||||
let[@inline] with_lock_ (self : t) f =
|
||||
Mutex.lock self.lock;
|
||||
match f self with
|
||||
| exception e ->
|
||||
Mutex.unlock self.lock;
|
||||
raise e
|
||||
| res ->
|
||||
Mutex.unlock self.lock;
|
||||
res
|
||||
|
||||
let clear (self : t) =
|
||||
let@ self = with_lock_ self in
|
||||
Heap.clear self.timer;
|
||||
Hashtbl.clear self.fds;
|
||||
self.sub_r <- [];
|
||||
self.sub_w <- [];
|
||||
self.sub_up_to_date <- true;
|
||||
()
|
||||
|
||||
(* TODO: *)
|
||||
let wakeup_from_outside _self : unit = ()
|
||||
|
||||
let get_fd_ (self : t) fd : per_fd =
|
||||
match Hashtbl.find self.fds fd with
|
||||
| per_fd -> per_fd
|
||||
| exception Not_found ->
|
||||
let per_fd = { fd; r = Nil; w = Nil } in
|
||||
Hashtbl.add self.fds fd per_fd;
|
||||
per_fd
|
||||
|
||||
let on_readable self fd x f : unit =
|
||||
let@ self = with_lock_ self in
|
||||
let per_fd = get_fd_ self fd in
|
||||
per_fd.r <- Sub (x, f, per_fd.r)
|
||||
|
||||
let on_writable self fd x f : unit =
|
||||
let@ self = with_lock_ self in
|
||||
let per_fd = get_fd_ self fd in
|
||||
per_fd.w <- Sub (x, f, per_fd.w)
|
||||
|
||||
let run_after_s self time x f : unit =
|
||||
let@ self = with_lock_ self in
|
||||
let deadline = now_ () +. time in
|
||||
Heap.insert self.timer (Timer { deadline; x; f })
|
||||
|
||||
let recompute_if_needed (self : t) =
|
||||
if not self.sub_up_to_date then (
|
||||
self.sub_up_to_date <- true;
|
||||
self.sub_r <- [];
|
||||
self.sub_w <- [];
|
||||
Hashtbl.iter
|
||||
(fun fd per_fd ->
|
||||
if cb_is_empty per_fd.r && cb_is_empty per_fd.w then
|
||||
Hashtbl.remove self.fds fd;
|
||||
if not (cb_is_empty per_fd.r) then self.sub_r <- fd :: self.sub_r;
|
||||
if not (cb_is_empty per_fd.w) then self.sub_w <- fd :: self.sub_w)
|
||||
self.fds
|
||||
)
|
||||
|
||||
(*
|
||||
let set fd (ev : Event.t) : unit =
|
||||
needs_recompute := true;
|
||||
match Hashtbl.find subs fd with
|
||||
| exception Not_found -> Hashtbl.add subs fd (ref ev)
|
||||
| old_ev -> old_ev := Event.(!old_ev lor ev)
|
||||
|
||||
let iter_ready f : unit = List.iter (fun (fd, ev) -> f fd ev) !ready_fds
|
||||
*)
|
||||
|
||||
let next_deadline_ (self : t) : float option =
|
||||
match Heap.peek_min_exn self.timer with
|
||||
| exception Heap.Empty -> None
|
||||
| Timer t -> Some t.deadline
|
||||
|
||||
let rec perform_cbs = function
|
||||
| Nil -> ()
|
||||
| Sub (x, f, tail) ->
|
||||
f x;
|
||||
perform_cbs tail
|
||||
|
||||
let step (self : t) : unit =
|
||||
(* gather the subscriptions and timeout *)
|
||||
let timeout, sub_r, sub_w =
|
||||
let@ self = with_lock_ self in
|
||||
recompute_if_needed self;
|
||||
let timeout =
|
||||
match next_deadline_ self with
|
||||
| None ->
|
||||
let has_waiters = self.sub_r != [] || self.sub_w != [] in
|
||||
if has_waiters then
|
||||
1e9
|
||||
else
|
||||
0.
|
||||
| Some d -> max 0. (d -. now_ ())
|
||||
in
|
||||
timeout, self.sub_r, self.sub_w
|
||||
in
|
||||
|
||||
let r_reads, r_writes, _ = Unix.select sub_r sub_w [] timeout in
|
||||
|
||||
(* gather the [per_fd] that are ready *)
|
||||
let ready_r = ref [] in
|
||||
let ready_w = ref [] in
|
||||
|
||||
(* gather the [per_fd] that have updates *)
|
||||
(let@ self = with_lock_ self in
|
||||
if r_reads != [] || r_writes != [] then self.sub_up_to_date <- false;
|
||||
|
||||
List.iter
|
||||
(fun fd ->
|
||||
let per_fd = Hashtbl.find self.fds fd in
|
||||
ready_r := per_fd :: !ready_r)
|
||||
r_reads;
|
||||
List.iter
|
||||
(fun fd ->
|
||||
let per_fd = Hashtbl.find self.fds fd in
|
||||
ready_w := per_fd :: !ready_w)
|
||||
r_writes);
|
||||
|
||||
(* call callbacks *)
|
||||
List.iter
|
||||
(fun fd ->
|
||||
perform_cbs fd.r;
|
||||
fd.r <- Nil)
|
||||
!ready_r;
|
||||
List.iter
|
||||
(fun fd ->
|
||||
perform_cbs fd.w;
|
||||
fd.w <- Nil)
|
||||
!ready_w;
|
||||
|
||||
()
|
||||
24
src/nanoev.mli
Normal file
24
src/nanoev.mli
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
(** Nano event loop *)
|
||||
|
||||
(*
|
||||
module type BACKEND = Intf.BACKEND
|
||||
|
||||
val unix : unit -> (module BACKEND)
|
||||
val create : ?backend:(module BACKEND) -> unit -> t
|
||||
*)
|
||||
|
||||
type t
|
||||
|
||||
val create : unit -> t
|
||||
|
||||
val clear : t -> unit
|
||||
(** Reset the state *)
|
||||
|
||||
val wakeup_from_outside : t -> unit
|
||||
|
||||
val step : t -> unit
|
||||
(** Run one step of the event loop until something happens *)
|
||||
|
||||
val on_readable : t -> Unix.file_descr -> 'a -> ('a -> unit) -> unit
|
||||
val on_writable : t -> Unix.file_descr -> 'a -> ('a -> unit) -> unit
|
||||
val run_after_s : t -> float -> 'a -> ('a -> unit) -> unit
|
||||
Loading…
Add table
Reference in a new issue