Callbacks vs Events - sgml/signature GitHub Wiki
Pattern Matching
S-Expr Features
Feature | S-Expressions (Lisp) | Python (3.13+) |
---|---|---|
Homoiconicity | Code = Data | Code ≠ Data |
Macro system | Built-in, hygienic | No native macro support |
Syntax manipulation | Easy via lists | Requires AST or string hacks |
Control structure extension | Declarative macros | Must use functions/decorators |
Pattern matching on code | Native via list destructuring | Structural pattern matching via match (Python ≥3.10) |
Use Cases for Pattern Matching on Code in Game Development
-
Event Dispatching
Usematch
to route game engine events (e.g. collisions, input, AI triggers) to appropriate handlers based on type and payload. -
Scripting Language Interpreters
Implement interpreters for in-game scripting (e.g. cutscene logic, NPC behavior trees) by pattern matching on AST nodes or token structures. -
State Machines
Model game states (e.g. menu, combat, dialogue) using pattern matching to cleanly handle transitions and actions. -
Command Parsers
Decode player or AI commands (e.g. "move", "attack", "use item") by matching structured input patterns. -
Animation and Behavior Trees
Traverse and evaluate declarative behavior trees or animation graphs using pattern matching on node types and conditions.
JavaScript
Most of the major JavaScript libraries claim to support custom events in one form or another. For example, jQuery, YUI and Dojo all support a custom “document ready” event. However, the implementation of these custom events is always some form of callback system.
A callback system works by storing event handlers in an array. When the underlying event is detected the dispatch system loops through the array calling the callback functions in turn. So what’s wrong with that? Before I answer that, let’s look at some code.
Here is some simple code that uses the DOMContentLoaded event to perform two separate initialisations:
document.addEventListener("DOMContentLoaded", function() {
console.log("Init: 1");
DOES_NOT_EXIST++; // this will throw an error
}, false);
document.addEventListener("DOMContentLoaded", function() {
console.log("Init: 2");
}, false);
What do you expect to see in the console when the document is loaded?
Well, you should see this (or something like it):
Init: 1
Error: DOES_NOT_EXIST is not defined
Init: 2
The point is, both functions are executed. You get an error in the first function but it does not stop the second function from executing.
The Problem
Now let’s look at some code based on a callback system. I’ll pick on jQuery because it’s the most popular:
$(document).ready(function() {
console.log("Init: 1");
DOES_NOT_EXIST++; // this will throw an error
});
$(document).ready(function() {
console.log("Init: 2");
});
What do we see in the console?
Init: 1
Error: DOES_NOT_EXIST is not defined
The problem is clear. Callback systems are brittle. If any of the callback functions throw an error then the subsequent callbacks are not executed. In reality, this means that a poorly written plugin can prevent other plugins from initialising.
Dojo suffers exactly the same problem as jQuery. The YUI library takes a slightly different approach. It wraps a try/catch around its dispatch mechanism. The downside is that your errors occur silently:
YAHOO.util.Event.onDOMReady(function() {
console.log("Init: 1");
DOES_NOT_EXIST++; // this will throw an error
});
YAHOO.util.Event.onDOMReady(function() {
console.log("Init: 2");
});
Produces:
Init: 1
Init: 2
Perfect initialisation! Nothing to worry about here! Except for the error that you don't see.
So what's the solution?
The Solution
The solution is to use a hybrid of a callback system and real event dispatch. We can trigger a fake event and from within that event, run the callback function. Each event handler has its own execution context. If an error occurs in our fake event then it won’t affect our callback system.
That sounds a bit complicated. I'll illustrate with some code.
var currentHandler;
if (document.addEventListener) {
document.addEventListener("fakeEvents", function() {
// execute the callback
currentHandler();
}, false);
var dispatchFakeEvent = function() {
var fakeEvent = document.createEvent("UIEvents");
fakeEvent.initEvent("fakeEvents", false, false);
document.dispatchEvent(fakeEvent);
};
} else { // MSIE
// I'll show this code later
}
var onLoadHandlers = [];
function addOnLoad(handler) {
onLoadHandlers.push(handler);
};
onload = function() {
for (var i = 0; i < onLoadHandlers.length; i++) {
currentHandler = onLoadHandlers[i];
dispatchFakeEvent();
}
};
Now we’ll use the code above to attach our two troublesome event handlers:
addOnLoad(function() {
console.log("Init: 1");
DOES_NOT_EXIST++; // this will throw an error
});
addOnLoad(function() {
console.log("Init: 2");
});
OK. Let’s run the code above and see what we get:
Init: 1
Error: DOES_NOT_EXIST is not defined
Init: 2
References
- https://developers.redhat.com/blog/2016/08/16/why-should-i-use-node-js-the-non-blocking-event-io-framework/
- https://docs.mongodb.com/v3.0/faq/concurrency/
- https://bjouhier.wordpress.com/2012/03/11/fibers-and-threads-in-node-js-what-for/
- https://www.dunebook.com/how-to-increase-performance-of-nodejs-application/
- https://nodeaddons.com/how-not-to-access-node-js-from-c-worker-threads/
- https://blog.risingstack.com/concurrency-and-parallelism-understanding-i-o/
- https://www.fpcomplete.com/blog/2016/12/concurrency-and-node
- https://mcculloughwebservices.com/2016/10/30/possible-eventemitter-memory-leak-detected/
- https://reactjs.org/docs/faq-functions.html
- https://www.webreflection.co.uk/blog/2015/10/22/how-to-add-dom-events-listeners
- https://github.com/facebook/react/issues/6901
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management
- https://github.com/zloirock/core-js/issues/86
- https://wiki.mozilla.org/Gecko:Overview
- https://firefox-source-docs.mozilla.org/toolkit/content/toolkit_widgets/ua_widget.html
- https://yahooeng.tumblr.com/post/122162710056/easier-instrumentation-with-react-i13n
- https://github.com/Microsoft/TypeScript/issues/1224
- https://www.easy-bits.com/blog/send-data-two-iframes
- https://github.com/angular/angular/issues/11200