Skip to content

Instantly share code, notes, and snippets.

@FaultyFunctions
Last active August 1, 2023 13:24

Revisions

  1. FaultyFunctions revised this gist Jan 17, 2022. 1 changed file with 63 additions and 43 deletions.
    106 changes: 63 additions & 43 deletions _EventBus.gml
    Original file line number Diff line number Diff line change
    @@ -25,50 +25,14 @@ global.EventBus = {
    /// @func on(event, callback, scope, [channel]);
    /// @desc subscribe to an event
    on: function(event, callback, scope, channel = global) {
    // RECURSE TO SUBSCRIBE FOR MULTIPLE EVENTS AT THE SAME TIME
    // AND YES I'M MAKING RECURSE A WORD
    if (is_array(event)) {
    for (var i = 0; i < array_length(event); ++i) {
    on(event[i], callback, scope, channel);
    }
    return;
    }

    if (channel == global) {
    channel = Channel.GLOBAL;
    }

    // CHECK IF CHANNEL EXISTS
    if (channel >= array_length(__channels)) {
    var _error = "Channel [ " + string(channel) + " ] does not exist! Be sure to add it to the Channel enum.";
    if (throw_errors) { throw _error; } else { show_debug_message(_error); }
    return;
    }

    // ENSURE WE HAVE A CALLBACK FUNCTION
    if (callback == undefined or typeof(callback) != "method") {
    var _error = "No callback defined, or callback isn't a function.";
    if (throw_errors) { throw _error; } else { show_debug_message(_error); }
    return;
    }

    // ENSURE EVENT ARRAY EXISTS ON CHANNEL
    if (!variable_struct_exists(__channels[channel], event)) {
    variable_struct_set(__channels[channel], event, []);
    }

    // CREATE WEAK REF
    var _inst_ref = undefined;
    with (scope) {
    _inst_ref = weak_ref_create(self);
    }
    __subscribe(event, callback, scope, channel);
    },
    /// @func once(event, callback, scope, [channel]);
    /// @desc subscribe to an event, but remove itself the subscription after being called once
    once: function(event, callback, scope, channel = global) {
    var _listener = __subscribe(event, callback, scope, channel);

    // ADD LISTENER TO EVENTS STRUCT
    array_push(__channels[channel][$ event], {
    reference: _inst_ref,
    callback: method(undefined, callback),
    isInstance: (instanceof(_inst_ref.ref) == "instance")
    });
    _listener.callOnce = true;
    },
    /// @func send(event, data, [channel]);
    /// @desc send an event with data
    @@ -107,11 +71,17 @@ global.EventBus = {
    if (_listener.isInstance) {
    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 {
    __pruneInstance(channel, event, i);
    }
    } else {
    method(_listener.reference.ref, _listener.callback)(data);
    if (_listener.callOnce) {
    remove(_listener.reference.ref, event, channel);
    }
    }
    }

    @@ -174,6 +144,56 @@ global.EventBus = {
    __pruneEventBus();
    }
    },
    __subscribe: function(event, callback, scope, channel = global) {
    // RECURSE TO SUBSCRIBE FOR MULTIPLE EVENTS AT THE SAME TIME
    // AND YES I'M MAKING RECURSE A WORD
    if (is_array(event)) {
    for (var i = 0; i < array_length(event); ++i) {
    on(event[i], callback, scope, channel);
    }
    return;
    }

    if (channel == global) {
    channel = Channel.GLOBAL;
    }

    // CHECK IF CHANNEL EXISTS
    if (channel >= array_length(__channels)) {
    var _error = "Channel [ " + string(channel) + " ] does not exist! Be sure to add it to the Channel enum.";
    if (throw_errors) { throw _error; } else { show_debug_message(_error); }
    return;
    }

    // ENSURE WE HAVE A CALLBACK FUNCTION
    if (callback == undefined or typeof(callback) != "method") {
    var _error = "No callback defined, or callback isn't a function.";
    if (throw_errors) { throw _error; } else { show_debug_message(_error); }
    return;
    }

    // ENSURE EVENT ARRAY EXISTS ON CHANNEL
    if (!variable_struct_exists(__channels[channel], event)) {
    variable_struct_set(__channels[channel], event, []);
    }

    // CREATE WEAK REF
    var _inst_ref = undefined;
    with (scope) {
    _inst_ref = weak_ref_create(self);
    }

    // ADD LISTENER TO EVENTS STRUCT
    var _listener = {
    reference: _inst_ref,
    callback: method(undefined, callback),
    isInstance: (instanceof(_inst_ref.ref) == "instance"),
    callOnce: false
    };
    array_push(__channels[channel][$ event], _listener);

    return _listener;
    },
    __pruneInstance: function(channel, event, index) {
    if (verbose) { show_debug_message("Pruned listener at index [ " + string(index) + " ] from [ " + string(event) + " ] because it no longer exists."); }
    array_delete(__channels[channel][$ event], index, 1);
  2. FaultyFunctions revised this gist Nov 22, 2021. 1 changed file with 13 additions and 15 deletions.
    28 changes: 13 additions & 15 deletions _EventBus.gml
    Original file line number Diff line number Diff line change
    @@ -86,10 +86,10 @@ global.EventBus = {

    // ENSURE EVENT EXISTS ON GLOBAL EVENTS STRUCT AND HAS LISTENERS
    if (!variable_struct_exists(__channels[channel], event)) {
    if (verbose) { show_debug_message("No listeners for event: " + event); }
    if (verbose) { show_debug_message("No listeners for event: " + string(event)); }
    return;
    } else if (array_length(__channels[channel][$ event]) == 0) {
    if (verbose) { show_debug_message("No listeners for event: " + event); }
    if (verbose) { show_debug_message("No listeners for event: " + string(event)); }
    return;
    }

    @@ -99,18 +99,16 @@ global.EventBus = {

    // IF OUR WEAK REFERENCES DIED, REMOVE THE INSTANCE AND MOVE ON
    if (!weak_ref_alive(_listener.reference)) {
    __pruneInstance(event, i);
    __pruneInstance(channel, event, i);
    continue;
    }

    // EXECUTE CALLBACKS
    if (_listener.isInstance) {
    if (instance_exists(_listener.reference.ref)) {
    with (_listener.reference.ref) {
    _listener.callback(data);
    }
    method(_listener.reference.ref.id, _listener.callback)(data);
    } else {
    __pruneInstance(event, i);
    __pruneInstance(channel, event, i);
    }
    } else {
    method(_listener.reference.ref, _listener.callback)(data);
    @@ -143,10 +141,10 @@ global.EventBus = {

    // ENSURE EVENT EXISTS ON GLOBAL EVENTS STRUCT AND HAS LISTENERS
    if (!variable_struct_exists(__channels[channel], event)) {
    if (verbose) { show_debug_message("Event [ " + event + " ] does not exist to remove listener from!"); }
    if (verbose) { show_debug_message("Event [ " + string(event) + " ] does not exist to remove listener from!"); }
    return;
    } else if (array_length(__channels[channel][$ event]) == 0) {
    if (verbose) { show_debug_message("Event [ " + event + " ] has no listeners to remove listener from!"); }
    if (verbose) { show_debug_message("Event [ " + string(event) + " ] has no listeners to remove listener from!"); }
    return;
    }

    @@ -162,7 +160,7 @@ global.EventBus = {
    array_delete(__channels[channel][$ event], i, 1); // Delete entry in array
    }
    } else {
    __pruneInstance(event, i);
    __pruneInstance(channel, event, i);
    }
    }

    @@ -176,8 +174,8 @@ global.EventBus = {
    __pruneEventBus();
    }
    },
    __pruneInstance: function(event, index) {
    if (verbose) { show_debug_message("Pruned listener at index [ " + index + " ] from [ " + event + " ] because it no longer exists."); }
    __pruneInstance: function(channel, event, index) {
    if (verbose) { show_debug_message("Pruned listener at index [ " + string(index) + " ] from [ " + string(event) + " ] because it no longer exists."); }
    array_delete(__channels[channel][$ event], index, 1);
    },
    __pruneEventBus: function() {
    @@ -193,15 +191,15 @@ global.EventBus = {
    for (var k = array_length(_event) - 1; k >= 0; --k) {
    var _listener = _event[k];
    if (!weak_ref_alive(_listener.reference)) {
    __pruneInstance(i, _event_list[j], k);
    delete _listener;
    __pruneInstance(_event, k);
    ++_pruned_listeners;
    }
    }
    if (array_length(_event) == 0) {
    variable_struct_remove(_channel, _event);
    variable_struct_remove(_channel, _event_list[j]);
    ++_pruned_events;
    if (verbose) { show_debug_message("Pruned [ " + _event + " ] due to no listeners."); }
    if (verbose) { show_debug_message("Pruned [ " + string(_event) + " ] due to no listeners."); }
    }
    }
    }
  3. FaultyFunctions revised this gist Aug 26, 2021. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion _EventBus.gml
    Original file line number Diff line number Diff line change
    @@ -180,7 +180,6 @@ global.EventBus = {
    if (verbose) { show_debug_message("Pruned listener at index [ " + index + " ] from [ " + event + " ] because it no longer exists."); }
    array_delete(__channels[channel][$ event], index, 1);
    },
    // #TODO: MAKE BETTER PRUNE EVENT THAT WORKS WITH CHANNELS AND MAYBE PRUNES OVER TIME
    __pruneEventBus: function() {
    if (verbose) { show_debug_message("Starting prune..."); }
    var _pruned_events = 0;
  4. FaultyFunctions revised this gist Aug 26, 2021. No changes.
  5. FaultyFunctions revised this gist Aug 26, 2021. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions _EventBus.gml
    Original file line number Diff line number Diff line change
    @@ -4,8 +4,6 @@
    // ADD YOUR CHANNELS HERE IN BEFORE _SIZE
    enum Channel {
    GLOBAL,
    ECS,
    PAUSE,
    _SIZE
    }

  6. FaultyFunctions revised this gist Aug 26, 2021. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion _EventBus.gml
    Original file line number Diff line number Diff line change
    @@ -191,7 +191,6 @@ global.EventBus = {
    for (var i = array_length(__channels) - 1; i >= 0; --i) {
    var _channel = __channels[i];
    var _event_list = variable_struct_get_names(_channel);
    log(_event_list);
    for (var j = array_length(_event_list) - 1; j >= 0; --j) {
    var _event = _channel[$ _event_list[j]];
    for (var k = array_length(_event) - 1; k >= 0; --k) {
  7. FaultyFunctions revised this gist Aug 26, 2021. 1 changed file with 54 additions and 51 deletions.
    105 changes: 54 additions & 51 deletions _EventBus.gml
    Original file line number Diff line number Diff line change
    @@ -18,13 +18,17 @@ enum GlobalEvent {
    // FOR YOUR EVENTS. CHANNELS SHOULD STRICTLY BE ENUMS.

    global.EventBus = {
    throw_errors: true,
    verbose: false,
    prune_threshold: 200,
    throw_errors: true, // Whether to crash the game or not if some funky stuff happens
    verbose: false, // Log messages in console of what's going on
    prune_threshold: 200, // Change this to have pruning run more or less often
    prune_count: 0,
    __channels: [],

    /// @func on(event, callback, scope, [channel]);
    /// @desc subscribe to an event
    on: function(event, callback, scope, channel = global) {
    // RECURSE TO SUBSCRIBE FOR MULTIPLE EVENTS AT THE SAME TIME
    // AND YES I'M MAKING RECURSE A WORD
    if (is_array(event)) {
    for (var i = 0; i < array_length(event); ++i) {
    on(event[i], callback, scope, channel);
    @@ -69,6 +73,7 @@ global.EventBus = {
    });
    },
    /// @func send(event, data, [channel]);
    /// @desc send an event with data
    send: function(event, data, channel = global) {
    if (channel == global) {
    channel = Channel.GLOBAL;
    @@ -91,7 +96,7 @@ global.EventBus = {
    }

    // SEND EVENT AND EXECUTE ANY CALLBACKS
    for (var i = 0; i < array_length(__channels[channel][$ event]); ++i) {
    for (var i = array_length(__channels[channel][$ event]) - 1; i >= 0; --i) {
    var _listener = __channels[channel][$ event][i];

    // IF OUR WEAK REFERENCES DIED, REMOVE THE INSTANCE AND MOVE ON
    @@ -115,16 +120,18 @@ global.EventBus = {
    }

    // PRUNE CHECK
    //if (++prune_count >= prune_threshold) {
    // __pruneEventBus();
    //}
    if (++prune_count >= prune_threshold) {
    __pruneEventBus();
    }
    },
    /// @func fire(event, [channel]);
    fire: function(event, channel) {
    /// @desc send an event without passing any data
    fire: function(event, channel = global) {
    send(event, undefined, channel);
    },
    /// @func off(instance, event, [channel]);
    off: function(instance, event, channel) {
    /// @func remove(instance, event, [channel]);
    /// @desc unsubscribe to an event
    remove: function(instance, event, channel = global) {
    if (channel == global) {
    channel = Channel.GLOBAL;
    }
    @@ -138,18 +145,21 @@ global.EventBus = {

    // ENSURE EVENT EXISTS ON GLOBAL EVENTS STRUCT AND HAS LISTENERS
    if (!variable_struct_exists(__channels[channel], event)) {
    if (verbose) { show_debug_message("Event [ " + event + " ] has no listeners to remove!"); }
    if (verbose) { show_debug_message("Event [ " + event + " ] does not exist to remove listener from!"); }
    return;
    } else if (array_length(__channels[channel][$ event]) == 0) {
    if (verbose) { show_debug_message("Event [ " + event + " ] has no listeners to remove!"); }
    if (verbose) { show_debug_message("Event [ " + event + " ] has no listeners to remove listener from!"); }
    return;
    }

    // CHECK FOR AND REMOVE INSTANCE FROM EVENT
    // CHECK FOR AND REMOVE INSTANCE FROM EVENT (also prunes that specific event list)
    for (var i = array_length(__channels[channel][$ event]) - 1; i >= 0; --i) {
    var _listener = __channels[channel][$ event][i];
    if (weak_ref_alive(_listener.reference)) {
    if (instance == _listener.reference.ref) {
    if (is_numeric(instance) and _listener.isInstance and instance == _listener.reference.ref.id) {
    delete __channels[channel][$ event][i]; // Delete struct from memory
    array_delete(__channels[channel][$ event], i, 1); // Delete entry in array
    } else if (instance == _listener.reference.ref) {
    delete __channels[channel][$ event][i]; // Delete struct from memory
    array_delete(__channels[channel][$ event], i, 1); // Delete entry in array
    }
    @@ -158,59 +168,52 @@ global.EventBus = {
    }
    }

    // PRUNE EVENT IF NO MORE LISTENERS
    // DELETE EVENT IF NO MORE LISTENERS
    if (array_length(__channels[channel][$ event]) == 0) {
    variable_struct_remove(__channels[channel], event);
    }

    // PRUNE CHECK
    //if (++prune_count >= prune_threshold) {
    // __pruneEventBus();
    //}
    },
    __init: function() {
    // DELETE OLD CHANNELS ARRAY
    array_resize(__channels, 0);

    // CREATE NEW ONE
    repeat(Channel._SIZE) {
    array_push(__channels, {});
    if (++prune_count >= prune_threshold) {
    __pruneEventBus();
    }
    },
    __pruneInstance: function(event, index) {
    if (verbose) { show_debug_message("Instance no longer exists! Pruning..."); }
    if (verbose) { show_debug_message("Pruned listener at index [ " + index + " ] from [ " + event + " ] because it no longer exists."); }
    array_delete(__channels[channel][$ event], index, 1);
    },
    // #TODO: MAKE BETTER PRUNE EVENT THAT WORKS WITH CHANNELS AND MAYBE PRUNES OVER TIME
    __pruneEventBus: function() {
    if (verbose) { show_debug_message("Starting prune..."); }
    var _prunedEvents = 0;
    var _prunedListeners = 0;

    var _eventList = variable_struct_get_names(__global_events);
    for (var i = array_length(_eventList) - 1; i >= 0; --i) {
    var _event = _eventList[i];

    // REMOVE IF NO LISTENERS
    if (array_length(__global_events[$ _event]) == 0) {
    variable_struct_remove(__global_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(__global_events[$ _event]) - 1; j >= 0; --j) {
    var _listener = __global_events[$ _event][j];
    if (!weak_ref_alive(_listener.reference)) {
    delete _listener;
    array_delete(__global_events[$ _event], j, 1);
    _prunedListeners++;
    var _pruned_events = 0;
    var _pruned_listeners = 0;

    for (var i = array_length(__channels) - 1; i >= 0; --i) {
    var _channel = __channels[i];
    var _event_list = variable_struct_get_names(_channel);
    log(_event_list);
    for (var j = array_length(_event_list) - 1; j >= 0; --j) {
    var _event = _channel[$ _event_list[j]];
    for (var k = array_length(_event) - 1; k >= 0; --k) {
    var _listener = _event[k];
    if (!weak_ref_alive(_listener.reference)) {
    delete _listener;
    __pruneInstance(_event, k);
    ++_pruned_listeners;
    }
    }
    if (array_length(_event) == 0) {
    variable_struct_remove(_channel, _event);
    ++_pruned_events;
    if (verbose) { show_debug_message("Pruned [ " + _event + " ] due to no listeners."); }
    }
    }
    }
    if (verbose) { show_debug_message("Pruned " + string(_prunedEvents) + " events and " + string(_prunedListeners) + " listeners."); }
    if (verbose) { show_debug_message("Pruned " + string(_pruned_events) + " events and " + string(_pruned_listeners) + " listeners."); }
    }
    }

    global.EventBus.__init();
    // INIT EVENT BUS CHANNELS ARRAY
    repeat(Channel._SIZE) {
    array_push(global.EventBus.__channels, {});
    }
  8. FaultyFunctions revised this gist Jul 25, 2021. 1 changed file with 111 additions and 53 deletions.
    164 changes: 111 additions & 53 deletions _EventBus.gml
    Original file line number Diff line number Diff line change
    @@ -1,61 +1,98 @@
    // CHANGE THIS IF YOU WANT TO CALL "global.EventBus" DIFFERENTLY
    #macro Event global.EventBus

    // ADD YOUR CHANNELS HERE IN BEFORE _SIZE
    enum Channel {
    GLOBAL,
    ECS,
    PAUSE,
    _SIZE
    }

    // ADD YOUR GLOBAL EVENTS HERE
    enum GlobalEvent {

    }

    // YOU CAN ALSO PASS IN STRINGS FOR EVENTS BUT IT'S BEST TO DEFINE ENUMS
    // FOR YOUR EVENTS. CHANNELS SHOULD STRICTLY BE ENUMS.

    global.EventBus = {
    error_messages: true,
    throw_errors: 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!"); }
    __channels: [],
    /// @func on(event, callback, scope, [channel]);
    on: function(event, callback, scope, channel = global) {
    if (is_array(event)) {
    for (var i = 0; i < array_length(event); ++i) {
    on(event[i], callback, scope, channel);
    }
    return;
    }

    if (channel == global) {
    channel = Channel.GLOBAL;
    }

    // CHECK IF CHANNEL EXISTS
    if (channel >= array_length(__channels)) {
    var _error = "Channel [ " + string(channel) + " ] does not exist! Be sure to add it to the Channel enum.";
    if (throw_errors) { throw _error; } else { show_debug_message(_error); }
    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."); }
    var _error = "No callback defined, or callback isn't a function.";
    if (throw_errors) { throw _error; } else { show_debug_message(_error); }
    return;
    }

    // ENSURE EVENT EXISTS ON GLOBAL EVENTS STRUCT
    if (!variable_struct_exists(__events, event)) {
    variable_struct_set(__events, event, []);
    // ENSURE EVENT ARRAY EXISTS ON CHANNEL
    if (!variable_struct_exists(__channels[channel], event)) {
    variable_struct_set(__channels[channel], event, []);
    }

    // CREATE WEAK REF
    var _inst_ref = undefined;
    with (instance) {
    with (scope) {
    _inst_ref = weak_ref_create(self);
    }

    // ADD LISTENER TO EVENTS STRUCT
    array_push(__events[$ event], {
    array_push(__channels[channel][$ 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!"); }
    /// @func send(event, data, [channel]);
    send: function(event, data, channel = global) {
    if (channel == global) {
    channel = Channel.GLOBAL;
    }

    // CHECK IF CHANNEL EXISTS
    if (channel >= array_length(__channels)) {
    var _error = "Channel [ " + string(channel) + " ] does not exist! Be sure to add it to the Channel enum.";
    if (throw_errors) { throw _error; } else { show_debug_message(_error); }
    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); }
    if (!variable_struct_exists(__channels[channel], event)) {
    if (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); }
    } else if (array_length(__channels[channel][$ event]) == 0) {
    if (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];
    for (var i = 0; i < array_length(__channels[channel][$ event]); ++i) {
    var _listener = __channels[channel][$ event][i];

    // IF OUR WEAK REFERENCES DIED, REMOVE THE INSTANCE AND MOVE ON
    if (!weak_ref_alive(_listener.reference)) {
    @@ -78,81 +115,102 @@ global.EventBus = {
    }

    // PRUNE CHECK
    if (++prune_count >= prune_threshold) {
    __pruneEventBus();
    }
    //if (++prune_count >= prune_threshold) {
    // __pruneEventBus();
    //}
    },
    /// @func fire(event, [channel]);
    fire: function(event, channel) {
    send(event, undefined, channel);
    },
    /// @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!"); }
    /// @func off(instance, event, [channel]);
    off: function(instance, event, channel) {
    if (channel == global) {
    channel = Channel.GLOBAL;
    }

    // CHECK IF CHANNEL EXISTS
    if (channel >= array_length(__channels)) {
    var _error = "Channel [ " + string(channel) + " ] does not exist! Be sure to add it to the Channel enum.";
    if (throw_errors) { throw _error; } else { show_debug_message(_error); }
    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!"); }
    if (!variable_struct_exists(__channels[channel], event)) {
    if (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!"); }
    } else if (array_length(__channels[channel][$ event]) == 0) {
    if (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];
    for (var i = array_length(__channels[channel][$ event]) - 1; i >= 0; --i) {
    var _listener = __channels[channel][$ 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
    delete __channels[channel][$ event][i]; // Delete struct from memory
    array_delete(__channels[channel][$ 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);
    if (array_length(__channels[channel][$ event]) == 0) {
    variable_struct_remove(__channels[channel], event);
    }

    // PRUNE CHECK
    if (++prune_count >= prune_threshold) {
    __pruneEventBus();
    //if (++prune_count >= prune_threshold) {
    // __pruneEventBus();
    //}
    },
    __init: function() {
    // DELETE OLD CHANNELS ARRAY
    array_resize(__channels, 0);

    // CREATE NEW ONE
    repeat(Channel._SIZE) {
    array_push(__channels, {});
    }
    },
    __pruneInstance: function(event, index) {
    if (verbose) { show_debug_message("Instance no longer exists! Pruning..."); }
    array_delete(__events[$ event], index, 1);
    array_delete(__channels[channel][$ event], index, 1);
    },
    // #TODO: MAKE BETTER PRUNE EVENT THAT WORKS WITH CHANNELS AND MAYBE PRUNES OVER TIME
    __pruneEventBus: function() {
    if (verbose) { show_debug_message("Starting prune..."); }
    var _prunedEvents = 0;
    var _prunedListeners = 0;

    var _eventList = variable_struct_get_names(__events);
    var _eventList = variable_struct_get_names(__global_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);
    if (array_length(__global_events[$ _event]) == 0) {
    variable_struct_remove(__global_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];
    for (var j = array_length(__global_events[$ _event]) - 1; j >= 0; --j) {
    var _listener = __global_events[$ _event][j];
    if (!weak_ref_alive(_listener.reference)) {
    delete _listener;
    array_delete(__events[$ _event], j, 1);
    array_delete(__global_events[$ _event], j, 1);
    _prunedListeners++;
    }
    }
    }
    if (verbose) { show_debug_message("Pruned " + string(_prunedEvents) + " events and " + string(_prunedListeners) + " listeners."); }
    }
    }
    }

    global.EventBus.__init();
  9. FaultyFunctions revised this gist Jul 23, 2021. 1 changed file with 61 additions and 18 deletions.
    79 changes: 61 additions & 18 deletions _EventBus.gml
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,8 @@
    global.EventBus = {
    error_messages: true,
    verbose: false,
    prune_threshold: 200,
    prune_count: 0,
    __events: {},
    /// @func addListener(instance, event, callback);
    addListener: function(instance, event, callback) {
    @@ -22,16 +24,16 @@ global.EventBus = {
    }

    // CREATE WEAK REF
    var _ref = undefined;
    var _inst_ref = undefined;
    with (instance) {
    _ref = weak_ref_create(self);
    _inst_ref = weak_ref_create(self);
    }

    // ADD LISTENER TO EVENTS STRUCT
    array_push(__events[$ event], {
    reference: _ref,
    callback: callback,
    isInstance: (instanceof(_ref.ref) == "instance")
    reference: _inst_ref,
    callback: method(undefined, callback),
    isInstance: (instanceof(_inst_ref.ref) == "instance")
    });
    },
    /// @func send(event, data)
    @@ -54,24 +56,31 @@ global.EventBus = {
    // SEND EVENT AND EXECUTE ANY CALLBACKS
    for (var i = 0; i < array_length(__events[$ event]); ++i) {
    var _listener = __events[$ event][i];
    if (weak_ref_alive(_listener.reference)) {
    if (_listener.isInstance) {
    if (instance_exists(_listener.reference.ref)) {
    with (_listener.reference.ref) {
    _listener.callback(data);
    }
    } else {
    __pruneInstance(event, i);
    }
    } else {

    // 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 {
    __pruneInstance(event, i);
    method(_listener.reference.ref, _listener.callback)(data);
    }
    }

    // PRUNE CHECK
    if (++prune_count >= prune_threshold) {
    __pruneEventBus();
    }
    },
    /// @func removeListener(instance, event)
    removeListener: function(instance, event) {
    @@ -107,9 +116,43 @@ global.EventBus = {
    if (array_length(__events[$ event]) == 0) {
    variable_struct_remove(__events, event);
    }

    // PRUNE CHECK
    if (++prune_count >= prune_threshold) {
    __pruneEventBus();
    }
    },
    __pruneInstance: function(event, index) {
    if (error_messages and verbose) { show_debug_message("Instance no longer exists! Pruning..."); }
    array_delete(__events[$ event], index, 1);
    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."); }
    }
    }
  10. FaultyFunctions revised this gist Jul 23, 2021. 2 changed files with 115 additions and 83 deletions.
    83 changes: 0 additions & 83 deletions EventBus.gml
    Original file line number Diff line number Diff line change
    @@ -1,83 +0,0 @@
    global.EventBus = {
    __events: {},
    /// @func addListener(instance, event, callback);
    addListener: function(instance, event, callback) {
    // ENSURE EVENT IS A STRING
    if (typeof(event) != "string") {
    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") {
    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, []);
    }

    // ADD LISTENER TO EVENTS STRUCT
    array_push(__events[$ event], {
    instance: instance,
    callback: callback
    });
    },
    /// @func send(event, data)
    send: function(event, data) {
    // ENSURE EVENT IS A STRING
    if (typeof(event) != "string") {
    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)) {
    show_debug_message("No listeners for event: " + event);
    return;
    } else if (array_length(__events[$ event]) == 0) {
    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];
    with (_listener.instance) {
    _listener.callback(data);
    }
    }
    },
    /// @func removeListener(instance, event)
    removeListener: function(instance, event) {
    // ENSURE EVENT IS A STRING
    if (typeof(event) != "string") {
    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)) {
    show_debug_message("Event [ " + event + " ] has no listeners to remove!");
    return;
    } else if (array_length(__events[$ event]) == 0) {
    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) {
    if (instance == __events[$ event][i].instance) {
    delete __events[$ event][i]; // Delete struct from memory
    array_delete(__events[$ event], i, 1); // Delete entry in array
    }
    }

    // PRUNE EVENT IF NO MORE LISTENERS
    if (array_length(__events[$ event]) == 0) {
    variable_struct_remove(__events, event);
    }
    }
    }
    115 changes: 115 additions & 0 deletions _EventBus.gml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,115 @@
    global.EventBus = {
    error_messages: true,
    verbose: false,
    __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 _ref = undefined;
    with (instance) {
    _ref = weak_ref_create(self);
    }

    // ADD LISTENER TO EVENTS STRUCT
    array_push(__events[$ event], {
    reference: _ref,
    callback: callback,
    isInstance: (instanceof(_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 (weak_ref_alive(_listener.reference)) {
    if (_listener.isInstance) {
    if (instance_exists(_listener.reference.ref)) {
    with (_listener.reference.ref) {
    _listener.callback(data);
    }
    } else {
    __pruneInstance(event, i);
    }
    } else {
    with (_listener.reference.ref) {
    _listener.callback(data);
    }
    }
    } else {
    __pruneInstance(event, i);
    }
    }
    },
    /// @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);
    }
    },
    __pruneInstance: function(event, index) {
    if (error_messages and verbose) { show_debug_message("Instance no longer exists! Pruning..."); }
    array_delete(__events[$ event], index, 1);
    }
    }
  11. FaultyFunctions created this gist Jul 23, 2021.
    83 changes: 83 additions & 0 deletions EventBus.gml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,83 @@
    global.EventBus = {
    __events: {},
    /// @func addListener(instance, event, callback);
    addListener: function(instance, event, callback) {
    // ENSURE EVENT IS A STRING
    if (typeof(event) != "string") {
    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") {
    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, []);
    }

    // ADD LISTENER TO EVENTS STRUCT
    array_push(__events[$ event], {
    instance: instance,
    callback: callback
    });
    },
    /// @func send(event, data)
    send: function(event, data) {
    // ENSURE EVENT IS A STRING
    if (typeof(event) != "string") {
    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)) {
    show_debug_message("No listeners for event: " + event);
    return;
    } else if (array_length(__events[$ event]) == 0) {
    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];
    with (_listener.instance) {
    _listener.callback(data);
    }
    }
    },
    /// @func removeListener(instance, event)
    removeListener: function(instance, event) {
    // ENSURE EVENT IS A STRING
    if (typeof(event) != "string") {
    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)) {
    show_debug_message("Event [ " + event + " ] has no listeners to remove!");
    return;
    } else if (array_length(__events[$ event]) == 0) {
    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) {
    if (instance == __events[$ event][i].instance) {
    delete __events[$ event][i]; // Delete struct from memory
    array_delete(__events[$ event], i, 1); // Delete entry in array
    }
    }

    // PRUNE EVENT IF NO MORE LISTENERS
    if (array_length(__events[$ event]) == 0) {
    variable_struct_remove(__events, event);
    }
    }
    }