Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/arr/compiler/compile-structs.arr
Original file line number Diff line number Diff line change
Expand Up @@ -3317,14 +3317,20 @@ no-globals = globals([string-dict:], [string-dict:], [string-dict:])
reactor-optional-fields = [SD.string-dict:
"last-image", {(l): A.a-name(l, A.s-type-global("Function"))},
"on-tick", {(l): A.a-name(l, A.s-type-global("Function"))},
"on-tick-send", {(l): A.a-name(l, A.s-type-global("Function"))},
"to-draw", {(l): A.a-name(l, A.s-type-global("Function"))},
"on-key", {(l): A.a-name(l, A.s-type-global("Function"))},
"on-key-send", {(l): A.a-name(l, A.s-type-global("Function"))},
"on-raw-key", {(l): A.a-name(l, A.s-type-global("Function"))},
"on-raw-key-send", {(l): A.a-name(l, A.s-type-global("Function"))},
"on-mouse", {(l): A.a-name(l, A.s-type-global("Function"))},
"on-mouse-send", {(l): A.a-name(l, A.s-type-global("Function"))},
"on-receive", {(l): A.a-name(l, A.s-type-global("Function"))},
"on-receive-send", {(l): A.a-name(l, A.s-type-global("Function"))},
"stop-when", {(l): A.a-name(l, A.s-type-global("Function"))},
"seconds-per-tick", {(l): A.a-name(l, A.s-type-global("NumPositive"))},
"close-when-stop", {(l): A.a-name(l, A.s-type-global("Boolean"))},
"title", {(l): A.a-name(l, A.s-type-global("String"))},
"close-when-stop", {(l): A.a-name(l, A.s-type-global("Boolean"))}
]

reactor-fields = reactor-optional-fields.set("init", {(l): A.a-any(l)})
14 changes: 14 additions & 0 deletions src/arr/trove/reactor-events.arr
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,30 @@ type Modifiers = {
control :: Boolean
}

# TODO: add other functions that we need
type Connector<M> = {
handle-message-return :: (M -> Nothing)
}

data Event:
| time-tick
| mouse(x :: Number, y :: Number, kind :: String)
| keypress(key :: String)
| raw-key(symbol :: String, code :: Number, key :: String, action :: String, modifiers :: Modifiers)
end

data ConnectedEvent<M>:
| message(msg :: M)
| event(e :: Event)
end

data RawKeyEventType:
| key-up
| key-down
| key-press
end

data SendingHandlerResult<S, M>:
| update(new-state :: S)
| send(new-state :: S, to-send :: M)
end
171 changes: 134 additions & 37 deletions src/js/trove/reactors.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@
name: "ValueSkeleton" },
"Table": { tag: "name",
origin: { "import-type": "uri", uri: "builtin://global" },
name: "Table" }
name: "Table" },
"SendingHandlerResult": { tag: "name",
origin: { "import-type": "uri", uri: "builtin://reactor-events" },
name: "SendingHandlerResult" },
"ConnectedEvent": { tag: "name",
origin: { "import-type": "uri", uri: "builtin://reactor-events" },
name: "ConnectedEvent" },
},
values: {
"keypress": ["arrow", ["String"], ["local", "Event"]],
Expand All @@ -35,6 +41,17 @@
"key-down": ["local", "RawKeyEventType"],
"key-press": ["local", "RawKeyEventType"],

"update": ["forall", ["s", "m"],
["arrow", ["tid", "s"],
["tyapp", ["local", "SendingHandlerResult"], [["tid", "s"], ["tid", "m"]]]]],
"send": ["forall", ["s", "m"],
["arrow", [["tid", "s"], ["tid", "m"]],
["tyapp", ["local", "SendingHandlerResult"], [["tid", "s"], ["tid", "m"]]]]],
"message": ["forall", ["m"]
["arrow", [["tid", "m"]], ["tyapp", ["local", "ConnectedEvent"], [["tid", "m"]]]]],
"event": ["forall", ["m"]
["arrow", [["local", "Event"]], ["tyapp", ["local", "ConnectedEvent"], [["tid", "m"]]]]],

"get-value": ["forall", ["a"], ["arrow", ["RofA"], ["tid", "a"]]],
"get-instance": ["forall", ["a"], ["arrow", ["RofA"], ["tid", "a"]]],
"draw": ["forall", ["a"], ["arrow", ["RofA"], "Image"]],
Expand All @@ -50,7 +67,8 @@
aliases: {
"Event": "ReactorEvent",
"RawKeyEventType": "RawKeyEventType",
"Reactor": ["local", "Reactor"]
"Reactor": ["local", "Reactor"],
"SendingHandlerResult": "SendingHandlerResult",
},
datatypes: {
"Reactor": ["data", "Reactor", ["a"], [], {
Expand Down Expand Up @@ -99,6 +117,8 @@
};

var annEvent = gtf(reactorEvents, "Event");
var annConnectedEvent = gtf(reactorEvents, "ConnectedEvent");
var annSendingHandlerResult = gtf(reactorEvents, "SendingHandlerResult");
var annNatural = runtime.makeFlatPredAnn(runtime.Number, runtime.makeFunction(function(val) {
return jsnums.isInteger(val) && jsnums.greaterThanOrEqual(val, 0, runtime.NumberErrbacks);
}, "Natural Number"), "Natural Number");
Expand All @@ -112,10 +132,50 @@
}

var isEvent = gmf(reactorEvents, "is-Event");
var isConnectedEvent = gmf(reactorEvents, "is-ConnectedEvent");
var isSendingHandlerResult = gmf(reactorEvents, "is-SendingHandlerResult");
var externalInteractionHandler = null;
var setInteract = function(newInteract) {
externalInteractionHandler = newInteract;
}
function callOrError(handlers, handlerName, args, cb) {
if(handlers.hasOwnProperty(handlerName + "-send")) {
var funObj = handlers[handlerName + "-send"].app;
return runtime.safeCall(function() {
return funObj.apply(funObj, args);
}, cb, "react:" + handlerName);
}
if(handlers.hasOwnProperty(handlerName)) {
var funObj = handlers[handlerName].app;
return runtime.safeCall(function() {
return funObj.apply(funObj, args);
}, cb, "react:" + handlerName);
}
else {
runtime.ffi.throwMessageException("No " + handlerName + " handler defined");
}
}
function handleBaseEvent(handlers, init, event, cb) {
return runtime.ffi.cases(isEvent, "Event", event, {
keypress: function(key) {
return callOrError(handlers, "on-key", [init, key], cb);
},
"time-tick": function() {
return callOrError(handlers, "on-tick", [init], cb);
},
mouse: function(x, y, kind) {
return callOrError(handlers, "on-mouse", [init, x, y, kind], cb);
},
"raw-key": function(key, type, caps, shift, alt, command, control) {
// NOTE(joe): we intentionally don't use all the fields above, assuming
// that users of on-raw-key are OK with consuming an event object
// rather than the fields of the event. This is mainly because typing
// out 8 parameters is pretty unreasonable, and this fancy version
// will mainly be used by folks who have gone through at least Reactive
return callOrError(handlers, "on-raw-key", [init, event], cb);
}
});
}
var makeReactor = function(init, fields) {
runtime.ffi.checkArity(2, arguments, "reactor", false);
c1("make-reactor", fields, annObject);
Expand Down Expand Up @@ -201,6 +261,26 @@
return makeReactorRaw(newVal, handlers, tracing, trace.concat(thisInteractTrace));
}, "interact");
}),
"interact-connect": runtime.makeMethod1(function(self, connector) {
// connector:
// register-on-message: Message -> ...
// handle-message-return: Message -> void
// dispose: () -> void
// reconnect: () -> void
checkArity(2, arguments, "interact-connect", true);
let thisInteractTrace = [];
let tracer = null;
if (tracing) {
tracer = (newVal, oldVal, k) => {
thisInteractTrace.push(newVal);
k();
};
}
return runtime.safeCall(
() => externalInteractionHandler(init, handlers, tracer, connector),
(newVal) => makeReactorRaw(newVal, handlers, tracing, trace.concat(thisInteractTrace)),
"interact-connect");
}),
"start-trace": runtime.makeMethod0(function(self) {
checkArity(1, arguments, "start-trace", true);
return makeReactorRaw(init, handlers, true, [init]);
Expand Down Expand Up @@ -238,24 +318,14 @@
react: runtime.makeMethod1(function(self, event) {
checkArity(2, arguments, "react", true);
c1("react", event, annEvent);
function callOrError(handlerName, args) {
if(handlers.hasOwnProperty(handlerName)) {
var funObj = handlers[handlerName].app;
return runtime.safeCall(function() {
return funObj.apply(funObj, args);
}, function(newVal) {
if(tracing) {
var newTrace = trace.concat([newVal]);
}
else {
var newTrace = trace;
}
return makeReactorRaw(newVal, handlers, tracing, newTrace);
}, "react:" + handlerName);
function reactCallback(newVal) {
if(tracing) {
var newTrace = trace.concat([newVal]);
}
else {
runtime.ffi.throwMessageException("No " + handlerName + " handler defined");
var newTrace = trace;
}
return makeReactorRaw(newVal, handlers, tracing, newTrace);
}
return runtime.safeCall(function() {
if(handlers["stop-when"]) {
Expand All @@ -267,29 +337,52 @@
}, function(stop) {
if(stop) {
return self;
} else {
return handleBaseEvent(handlers, init, event, reactCallback);
}
else {
return runtime.ffi.cases(isEvent, "Event", event, {
keypress: function(key) {
return callOrError("on-key", [init, key]);
},
"time-tick": function() {
return callOrError("on-tick", [init]);
},
mouse: function(x, y, kind) {
return callOrError("on-mouse", [init, x, y, kind]);
},
"raw-key": function(key, type, caps, shift, alt, command, control) {
// NOTE(joe): we intentionally don't use all the fields above, assuming
// that users of on-raw-key are OK with consuming an event object
// rather than the fields of the event. This is mainly because typing
// out 8 parameters is pretty unreasonable, and this fancy version
// will mainly be used by folks who have gone through at least Reactive
return callOrError("on-raw-key", [init, event]);
}
}, "react:stop-when");
}),
respond: runtime.makeMethod2(function(self, event, connector) {
checkArity(3, arguments, "react", true);
c1("respond", event, annConnectedEvent);
function respondCallback(newVal) {
return runtime.ffi.cases(isSendingHandlerResult, "SendingHandlerResult", newVal, {
update: (val) =>
runtime.makeTuple([
makeReactorRaw(val, handlers, tracing, trace.concat([val])),
runtime.ffi.makeNone(),
]),
send: (val, msg) =>
runtime.safeCall(
() => gf(connector, "handle-message-return").app(msg),
() =>
runtime.makeTuple([
makeReactorRaw(val, handlers, tracing, trace.concat([val])),
runtime.ffi.makeSome(msg),
]),
),
});
}
return runtime.safeCall(
() => {
if (handlers["stop-when"]) {
return handlers["stop-when"].app(init);
} else {
return false;
}
},
(stop) => {
if (stop) {
return self;
} else {
return runtime.ffi.cases(isConnectedEvent, "ConnectedEvent", event, {
message: (msg) =>
callOrError(handlers, "on-receive", [init, msg], respondCallback),
event: (e) => handleBaseEvent(handlers, init, e, respondCallback),
});
}
}, "react:stop-when");
}
)
}),
"is-stopped": runtime.makeMethod0(function(self) {
checkArity(1, arguments, "is-stopped", true);
Expand Down Expand Up @@ -381,6 +474,10 @@
"key-down": gmf(reactorEvents, "key-down"),
"key-press": gmf(reactorEvents, "key-press"),
"make-reactor": F(makeReactor, "make-reactor"),
"update": gmf(reactorEvents, "update"),
"send": gmf(reactorEvents, "send"),
"message": gmf(reactorEvents, "message"),
"event": gmf(reactorEvents, "event"),

"get-value": F(getValue, "get-value"),
"get-instance": F(getValue, "get-instance"),
Expand Down