global.EventBus = { | |
error_messages: true, | |
verbose: false, | |
prune_threshold: 200, | |
prune_count: 0, | |
__events: {}, | |
/// @func addListener(instance, event, callback); | |
addListener: function(instance, event, callback) { | |
// ENSURE EVENT IS A STRING | |
if (typeof(event) != "string") { | |
if (error_messages) { show_debug_message("Attempting to listen for event that is not a string!"); } | |
return; | |
} | |
// ENSURE WE HAVE A CALLBACK FUNCTION | |
if (callback == undefined or typeof(callback) != "method") { | |
if (error_messages) { show_debug_message("No callback defined, or callback isn't a method."); } | |
return; | |
} | |
// ENSURE EVENT EXISTS ON GLOBAL EVENTS STRUCT | |
if (!variable_struct_exists(__events, event)) { | |
variable_struct_set(__events, event, []); | |
} | |
// CREATE WEAK REF | |
var _inst_ref = undefined; | |
with (instance) { | |
_inst_ref = weak_ref_create(self); | |
} | |
// ADD LISTENER TO EVENTS STRUCT | |
array_push(__events[$ event], { | |
reference: _inst_ref, | |
callback: method(undefined, callback), | |
isInstance: (instanceof(_inst_ref.ref) == "instance") | |
}); | |
}, | |
/// @func send(event, data) | |
send: function(event, data) { | |
// ENSURE EVENT IS A STRING | |
if (typeof(event) != "string") { | |
if (error_messages) { show_debug_message("Given event is not a string!"); } | |
return; | |
} | |
// ENSURE EVENT EXISTS ON GLOBAL EVENTS STRUCT AND HAS LISTENERS | |
if (!variable_struct_exists(__events, event)) { | |
if (error_messages and verbose) { show_debug_message("No listeners for event: " + event); } | |
return; | |
} else if (array_length(__events[$ event]) == 0) { | |
if (error_messages and verbose) { show_debug_message("No listeners for event: " + event); } | |
return; | |
} | |
// SEND EVENT AND EXECUTE ANY CALLBACKS | |
for (var i = 0; i < array_length(__events[$ event]); ++i) { | |
var _listener = __events[$ event][i]; | |
// IF OUR WEAK REFERENCES DIED, REMOVE THE INSTANCE AND MOVE ON | |
if (!weak_ref_alive(_listener.reference)) { | |
__pruneInstance(event, i); | |
continue; | |
} | |
// EXECUTE CALLBACKS | |
if (_listener.isInstance) { | |
if (instance_exists(_listener.reference.ref)) { | |
with (_listener.reference.ref) { | |
_listener.callback(data); | |
} | |
} else { | |
__pruneInstance(event, i); | |
} | |
} else { | |
method(_listener.reference.ref, _listener.callback)(data); | |
} | |
} | |
// PRUNE CHECK | |
if (++prune_count >= prune_threshold) { | |
__pruneEventBus(); | |
} | |
}, | |
/// @func removeListener(instance, event) | |
removeListener: function(instance, event) { | |
// ENSURE EVENT IS A STRING | |
if (typeof(event) != "string") { | |
if (error_messages) { show_debug_message("Attempting to remove event that is not a string!"); } | |
return; | |
} | |
// ENSURE EVENT EXISTS ON GLOBAL EVENTS STRUCT AND HAS LISTENERS | |
if (!variable_struct_exists(__events, event)) { | |
if (error_messages and verbose) { show_debug_message("Event [ " + event + " ] has no listeners to remove!"); } | |
return; | |
} else if (array_length(__events[$ event]) == 0) { | |
if (error_messages and verbose) { show_debug_message("Event [ " + event + " ] has no listeners to remove!"); } | |
return; | |
} | |
// CHECK FOR AND REMOVE INSTANCE FROM EVENT | |
for (var i = array_length(__events[$ event]) - 1; i >= 0; --i) { | |
var _listener = __events[$ event][i]; | |
if (weak_ref_alive(_listener.reference)) { | |
if (instance == _listener.reference.ref) { | |
delete __events[$ event][i]; // Delete struct from memory | |
array_delete(__events[$ event], i, 1); // Delete entry in array | |
} | |
} else { | |
__pruneInstance(event, i); | |
} | |
} | |
// PRUNE EVENT IF NO MORE LISTENERS | |
if (array_length(__events[$ event]) == 0) { | |
variable_struct_remove(__events, event); | |
} | |
// PRUNE CHECK | |
if (++prune_count >= prune_threshold) { | |
__pruneEventBus(); | |
} | |
}, | |
__pruneInstance: function(event, index) { | |
if (verbose) { show_debug_message("Instance no longer exists! Pruning..."); } | |
array_delete(__events[$ event], index, 1); | |
}, | |
__pruneEventBus: function() { | |
if (verbose) { show_debug_message("Starting prune..."); } | |
var _prunedEvents = 0; | |
var _prunedListeners = 0; | |
var _eventList = variable_struct_get_names(__events); | |
for (var i = array_length(_eventList) - 1; i >= 0; --i) { | |
var _event = _eventList[i]; | |
// REMOVE IF NO LISTENERS | |
if (array_length(__events[$ _event]) == 0) { | |
variable_struct_remove(__events, _event); | |
_prunedEvents++; | |
if (verbose) { show_debug_message("Pruned [ " + _event + " ] due to no listeners."); } | |
continue; | |
} | |
// ENTER EACH EVENT AND REMOVE DEAD REFERENCES | |
for (var j = array_length(__events[$ _event]) - 1; j >= 0; --j) { | |
var _listener = __events[$ _event][j]; | |
if (!weak_ref_alive(_listener.reference)) { | |
delete _listener; | |
array_delete(__events[$ _event], j, 1); | |
_prunedListeners++; | |
} | |
} | |
} | |
if (verbose) { show_debug_message("Pruned " + string(_prunedEvents) + " events and " + string(_prunedListeners) + " listeners."); } | |
} | |
} |
Brilliant! I like this a lot and would like to use it in a commercial game. What's the license? I sent you a DM on Twitter.
I already replied on Twitter but for anyone else coming across this, feel free to use this on any project for free. If you really want you can add a credit for "FaultyFunctions" but it is definitely not required!
I've found what might be a bug, or it might be intended behaviour.
When I deactivate an instance instance_exists
returns false. If an event is triggered that the deactivated instance is listening for, the listener will be pruned. For my purposes this is not desirable behaviour because I want the instance to receive events when it is re-activated. I've removed lines 77 and 78 to stop this happening.
if (instance_exists(_listener.reference.ref)) {
method(_listener.reference.ref.id, _listener.callback)(data);
if (_listener.callOnce) {
remove(_listener.reference.ref.id, event, channel);
}
} else { // line 77
__pruneInstance(channel, event, i); // line 78
}
Otherwise this is working very well for me ❤️
I've found what might be a bug, or it might be intended behaviour.
When I deactivate an instance
instance_exists
returns false. If an event is triggered that the deactivated instance is listening for, the listener will be pruned. For my purposes this is not desirable behaviour because I want the instance to receive events when it is re-activated. I've removed lines 77 and 78 to stop this happening.if (instance_exists(_listener.reference.ref)) { method(_listener.reference.ref.id, _listener.callback)(data); if (_listener.callOnce) { remove(_listener.reference.ref.id, event, channel); } } else { // line 77 __pruneInstance(channel, event, i); // line 78 }
Otherwise this is working very well for me ❤️
Hello! Thanks for bringing this up. I guess I didn't really consider this but this is a good thing to point out. Since I don't really use the deactivate instance functions (I use my own personal version of them), The __pruneEventBus
function checks if a weak reference is still alive, I'm not 100% sure a weak ref to an instance is still alive if it's been deactivated so you removing that line might be the best solution depending on what weak_ref_alive()
returns in the __pruneEventBus
function. It's been a long time since I've looked at this code to be honest but thanks for bringing this up. I'll have to do some testing!
From my tests weak_ref_alive()
returns true
for deactivated instances and there's already a check for this on line 65.
I'm using instance_deactivate_layer
to deactivate the instances in question, and I've assumed that works the same as deactivating a single instance.
Thanks again 👍
Brilliant! I like this a lot and would like to use it in a commercial game. What's the license? I sent you a DM on Twitter.