diff --git a/src/syndicate.nim b/src/syndicate.nim index 5e99189..430b074 100644 --- a/src/syndicate.nim +++ b/src/syndicate.nim @@ -26,9 +26,6 @@ export asyncdispatch.`callback=` proc `==`*(x, y: FieldId): bool {.borrow.} -proc newLit(p: pointer): NimNode = ident"nil" - ## Hack to make `newLit` work on `Presevere`. - proc getCurrentFacet*(): Facet {.error.} ## Return the current `Facet` for this context. @@ -46,8 +43,8 @@ template sendMessage*(msg: untyped): untyped = mixin getCurrentFacet send(getCurrentFacet(), toPreserve(msg)) -proc callbackForEvent(event: EventKind; pattern, handler: NimNode): NimNode = - ## Generate a procedure that checks an event kind, unpacks `pattern` match to fit the +proc wrapHandler(handler: NimNode): NimNode = + ## Generate a procedure that unpacks a `pattern` match to fit the ## parameters of `handler`, and calls the body of `handler`. # TODO: compile time analysis of pattern to count number of captures handler.expectKind nnkDo @@ -55,8 +52,7 @@ proc callbackForEvent(event: EventKind; pattern, handler: NimNode): NimNode = formalArgs = handler[3] cbFacetSym = genSym(nskParam, "facet") scriptFacetSym = genSym(nskParam, "facet") - eventSym = genSym(nskParam, "event") - recSym = genSym(nskParam, "record") + recSym = genSym(nskParam, "bindings") var letSection = newNimNode(nnkLetSection, handler) captureCount: int @@ -101,31 +97,25 @@ proc callbackForEvent(event: EventKind; pattern, handler: NimNode): NimNode = params = [ newEmptyNode(), newIdentDefs(cbFacetSym, ident"Facet"), - newIdentDefs(eventSym, ident"EventKind"), newIdentDefs(recSym, newNimNode(nnkBracketExpr).add(ident"seq", ident"Preserve")), ], - body = newStmtList( - newIfStmt((cond: infix(eventSym, "==", newLit(event)), body: + body = newStmtList( script, newCall("scheduleScript", cbFacetSym, script[0]) - ))))) + )) # TODO: this proc just checks the event type and then schedules a script, # should the event check be done in skeletons instead? -proc onEvent(event: EventKind; pattern, handler: NimNode): NimNode = +proc onEvent(event: EventKind; pattern, doHandler: NimNode): NimNode = let - handler = callbackForEvent(event, pattern, handler) + handler = wrapHandler(doHandler) handlerSym = handler[0] result = quote do: `handler` mixin getCurrentFacet - discard getCurrentFacet().addEndpoint do (facet: Facet) -> EndpointSpec: - let a = `pattern` - result.assertion = some(toPreserve(observe(a))) - result.analysis = some(analyzeAssertion(a)) - result.analysis.get.callback = wrap(facet, `handlerSym`) + onEvent(getCurrentFacet(), `pattern`, EventKind(`event`), `handlerSym`) macro onAsserted*(pattern: Preserve; handler: untyped) = onEvent(addedEvent, pattern, handler) diff --git a/src/syndicate/dataspaces.nim b/src/syndicate/dataspaces.nim index 6ab2842..b4c9821 100644 --- a/src/syndicate/dataspaces.nim +++ b/src/syndicate/dataspaces.nim @@ -1,6 +1,6 @@ # SPDX-License-Identifier: ISC -import ./bags, ./dataflow, ./events, ./skeletons +import ./assertions, ./bags, ./dataflow, ./events, ./skeletons import preserves import std/[asyncdispatch, deques, hashes, macros, options, sets, tables] @@ -332,12 +332,6 @@ proc wrap(facet; script: Script[void]): Task[void] = proc task() = facet.invokeScript(script) task -proc wrap*(facet; cb: proc(facet: Facet; event: EventKind; bindings: seq[Value]) {.gcsafe.}): HandlerCallback = - proc wrapper(event: EventKind; bindings: seq[Value]) = - facet.invokeScript do (facet: Facet): - cb(facet, event, bindings) - wrapper - proc scheduleScript*(facet; prio: Priority; script: Script[void]) = facet.actor.scheduleTask(prio, facet.wrap(script)) @@ -573,3 +567,34 @@ template declareField*(facet: Facet; F: untyped; T: typedesc; initial: T): untyp proc getPreserve(f: DistinctField): Preserve {.used.} = facet.actor.dataspace.dataflow.recordObservation(f.id) facet.fields[fieldOff] + +type EventHandler* = proc (facet: Facet; bindings: seq[Value]) {.gcsafe.} + +proc wrap*(facet; onEvent: EventKind; cb: EventHandler): HandlerCallback = + proc wrapper(event: EventKind; bindings: seq[Value]) = + facet.invokeScript do (facet: Facet): + if event == onEvent: + facet.scheduleScript do (facet: Facet): + cb(facet, bindings) + wrapper + +proc onEvent*(facet; pattern: Preserve; event: EventKind; cb: EventHandler) = + discard facet.addEndpoint do (facet: Facet) -> EndpointSpec: + result.assertion = some(observe(pattern).toPreserve) + result.analysis = some(analyzeAssertion(pattern)) + result.analysis.get.callback = wrap(facet, event, cb) + +proc onAsserted*(facet; pattern: Preserve; cb: EventHandler) = + onEvent(facet, pattern, addedEvent, cb) + +proc onRetracted*(facet; pattern: Preserve; cb: EventHandler) = + onEvent(facet, pattern, removedEvent, cb) + +proc onMessage*(facet; pattern: Preserve; cb: EventHandler) = + onEvent(facet, pattern, messageEvent, cb) + +template stopIf*(facet: Facet; cond: untyped; continuation: Script[void]): untyped = + ## Stop the current facet if `cond` is true and + ## invoke `body` after the facet has stopped. + discard facet.addDataflow do (facet: Facet): + if cond: facet.stop(continuation) diff --git a/tests/test_box_and_client.nim b/tests/test_box_and_client.nim index 1c1842d..c30e6e6 100644 --- a/tests/test_box_and_client.nim +++ b/tests/test_box_and_client.nim @@ -27,43 +27,19 @@ proc boot(facet: Facet) = facet.stop do (facet: Facet): echo "terminated box root facet" - discard facet.addEndpoint do (facet: Facet) -> EndpointSpec: - const a = SetBox.init(`?*`) - result.analysis = some analyzeAssertion(a) - proc cb(facet: Facet; evt: EventKind; vs: seq[Value]) = - if evt == messageEvent: - facet.scheduleScript do (facet: Facet): - value.set(vs[0]) - echo "box updated value ", vs[0] - result.analysis.get.callback = facet.wrap cb - const o = observe(SetBox.init(`?*`)).toPreserve - result.assertion = some o + facet.onMessage(SetBox.init(`?*`)) do (facet: Facet; vs: seq[Value]): + value.set(vs[0]) + echo "box updated value ", vs[0] facet.spawn("client") do (facet: Facet): - discard facet.addEndpoint do (facet: Facet) -> EndpointSpec: - const a = BoxState.init(`?*`) - result.analysis = some analyzeAssertion(a) - proc cb(facet: Facet; evt: EventKind; vs: seq[Value]) = - if evt == addedEvent: - facet.scheduleScript do (facet: Facet): - let v = SetBox.init(vs[0].int.succ.toPreserve) - # echo "client sending ", v - facet.send(v) - result.analysis.get.callback = facet.wrap cb - const o = observe(BoxState.init(`?*`)).toPreserve - result.assertion = some o + facet.onAsserted(BoxState.init(`?*`)) do (facet: Facet; vs: seq[Value]): + let v = SetBox.init(vs[0].int.succ.toPreserve) + # echo "client sending ", v + facet.send(v) - discard facet.addEndpoint do (facet: Facet) -> EndpointSpec: - const a = BoxState.init(`?_`) - result.analysis = some analyzeAssertion(a) - proc cb(facet: Facet; evt: EventKind; vs: seq[Value]) = - if evt == removedEvent: - facet.scheduleScript do (facet: Facet): - echo "box gone" - result.analysis.get.callback = facet.wrap cb - const o = observe(BoxState.init(`?_`)).toPreserve - result.assertion = some o + facet.onRetracted(BoxState.init(`?_`)) do (facet: Facet; vs: seq[Value]): + echo "box gone" facet.actor.dataspace.ground.addStopHandler do (_: Dataspace): echo "stopping box-and-client"