mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 10:12:45 +00:00
239 lines
9.1 KiB
Plaintext
239 lines
9.1 KiB
Plaintext
//
|
|
// Observer Pattern Implementation
|
|
//
|
|
// Implements a basic observer pattern with the following main procs:
|
|
//
|
|
// /decl/observ/proc/is_listening(var/event_source, var/datum/listener, var/proc_call)
|
|
// event_source: The instance which is generating events.
|
|
// listener: The instance which may be listening to events by event_source
|
|
// proc_call: Optional. The specific proc to call when the event is raised.
|
|
//
|
|
// Returns true if listener is listening for events by event_source, and proc_call supplied is either null or one of the proc that will be called when an event is raised.
|
|
//
|
|
// /decl/observ/proc/has_listeners(var/event_source)
|
|
// event_source: The instance which is generating events.
|
|
//
|
|
// Returns true if the given event_source has any listeners at all, globally or to specific event sources.
|
|
//
|
|
// /decl/observ/proc/register(var/event_source, var/datum/listener, var/proc_call)
|
|
// event_source: The instance you wish to receive events from.
|
|
// listener: The instance/owner of the proc to call when an event is raised by the event_source.
|
|
// proc_call: The proc to call when an event is raised.
|
|
//
|
|
// It is possible to register the same listener to the same event_source multiple times as long as it is using different proc_calls.
|
|
// Registering again using the same event_source, listener, and proc_call that has been registered previously will have no additional effect.
|
|
// I.e.: The proc_call will still only be called once per raised event. That particular proc_call will only have to be unregistered once.
|
|
//
|
|
// When proc_call is called the first argument is always the source of the event (event_source).
|
|
// Additional arguments may or may not be supplied, see individual event definition files (destroyed.dm, moved.dm, etc.) for details.
|
|
//
|
|
// The instance making the register() call is also responsible for calling unregister(), see below for additonal details, including when event_source is destroyed.
|
|
// This can be handled by listening to the event_source's destroyed event, unregistering in the listener's Destroy() proc, etc.
|
|
//
|
|
// /decl/observ/proc/unregister(var/event_source, var/datum/listener, var/proc_call)
|
|
// event_source: The instance you wish to stop receiving events from.
|
|
// listener: The instance which will no longer receive the events.
|
|
// proc_call: Optional: The proc_call to unregister.
|
|
//
|
|
// Unregisters the listener from the event_source.
|
|
// If a proc_call has been supplied only that particular proc_call will be unregistered. If the proc_call isn't currently registered there will be no effect.
|
|
// If no proc_call has been supplied, the listener will have all registrations made to the given event_source undone.
|
|
//
|
|
// /decl/observ/proc/register_global(var/datum/listener, var/proc_call)
|
|
// listener: The instance/owner of the proc to call when an event is raised by any and all sources.
|
|
// proc_call: The proc to call when an event is raised.
|
|
//
|
|
// Works very much the same as register(), only the listener/proc_call will receive all relevant events from all event sources.
|
|
// Global registrations can overlap with registrations made to specific event sources and these will not affect each other.
|
|
//
|
|
// /decl/observ/proc/unregister_global(var/datum/listener, var/proc_call)
|
|
// listener: The instance/owner of the proc which will no longer receive the events.
|
|
// proc_call: Optional: The proc_call to unregister.
|
|
//
|
|
// Works very much the same as unregister(), only it undoes global registrations instead.
|
|
//
|
|
// /decl/observ/proc/raise_event(src, ...)
|
|
// Should never be called unless implementing a new event type.
|
|
// The first argument shall always be the event_source belonging to the event. Beyond that there are no restrictions.
|
|
|
|
/decl/observ
|
|
var/name = "Unnamed Event" // The name of this event, used mainly for debug/VV purposes. The list of event managers can be reached through the "Debug Controller" verb, selecting the "Observation" entry.
|
|
var/expected_type = /datum // The expected event source for this event. register() will CRASH() if it receives an unexpected type.
|
|
var/list/event_sources = list() // Associative list of event sources, each with their own associative list. This associative list contains an instance/list of procs to call when the event is raised.
|
|
var/list/global_listeners = list() // Associative list of instances that listen to all events of this type (as opposed to events belonging to a specific source) and the proc to call.
|
|
|
|
/decl/observ/New()
|
|
GLOB.all_observable_events.events += src
|
|
. = ..()
|
|
|
|
/decl/observ/proc/is_listening(var/event_source, var/datum/listener, var/proc_call)
|
|
// Return whether there are global listeners unless the event source is given.
|
|
if (!event_source)
|
|
return !!global_listeners.len
|
|
|
|
// Return whether anything is listening to a source, if no listener is given.
|
|
if (!listener)
|
|
return global_listeners.len || (event_source in event_sources)
|
|
|
|
// Return false if nothing is associated with that source.
|
|
if (!(event_source in event_sources))
|
|
return FALSE
|
|
|
|
// Get and check the listeners for the reuqested event.
|
|
var/listeners = event_sources[event_source]
|
|
if (!(listener in listeners))
|
|
return FALSE
|
|
|
|
// Return true unless a specific callback needs checked.
|
|
if (!proc_call)
|
|
return TRUE
|
|
|
|
// Check if the specific callback exists.
|
|
var/list/callback = listeners[listener]
|
|
if (!callback)
|
|
return FALSE
|
|
|
|
return (proc_call in callback)
|
|
|
|
/decl/observ/proc/has_listeners(var/event_source)
|
|
return is_listening(event_source)
|
|
|
|
/decl/observ/proc/register(var/datum/event_source, var/datum/listener, var/proc_call)
|
|
// Sanity checking.
|
|
if (!(event_source && listener && proc_call))
|
|
return FALSE
|
|
if (istype(event_source, /decl/observ))
|
|
return FALSE
|
|
|
|
// Crash if the event source is the wrong type.
|
|
if (!istype(event_source, expected_type))
|
|
CRASH("Unexpected type. Expected [expected_type], was [event_source.type]")
|
|
|
|
// Setup the listeners for this source if needed.
|
|
var/list/listeners = event_sources[event_source]
|
|
if (!listeners)
|
|
listeners = list()
|
|
event_sources[event_source] = listeners
|
|
|
|
// Make sure the callbacks are a list.
|
|
var/list/callbacks = listeners[listener]
|
|
if (!callbacks)
|
|
callbacks = list()
|
|
listeners[listener] = callbacks
|
|
|
|
// If the proc_call is already registered skip
|
|
if(proc_call in callbacks)
|
|
return FALSE
|
|
|
|
// Add the callback, and return true.
|
|
callbacks += proc_call
|
|
return TRUE
|
|
|
|
/decl/observ/proc/unregister(var/event_source, var/datum/listener, var/proc_call)
|
|
// Sanity.
|
|
if (!(event_source && listener && (event_source in event_sources)))
|
|
return FALSE
|
|
|
|
// Return false if nothing is listening for this event.
|
|
var/list/listeners = event_sources[event_source]
|
|
if (!listeners)
|
|
return FALSE
|
|
|
|
// Remove all callbacks if no specific one is given.
|
|
if (!proc_call)
|
|
if(listeners.Remove(listener))
|
|
// Perform some cleanup and return true.
|
|
if (!listeners.len)
|
|
event_sources -= event_source
|
|
return TRUE
|
|
return FALSE
|
|
|
|
// See if the listener is registered.
|
|
var/list/callbacks = listeners[listener]
|
|
if (!callbacks)
|
|
return FALSE
|
|
|
|
// See if the callback exists.
|
|
if(!callbacks.Remove(proc_call))
|
|
return FALSE
|
|
|
|
if (!callbacks.len)
|
|
listeners -= listener
|
|
if (!listeners.len)
|
|
event_sources -= event_source
|
|
return TRUE
|
|
|
|
/decl/observ/proc/register_global(var/datum/listener, var/proc_call)
|
|
// Sanity.
|
|
if (!(listener && proc_call))
|
|
return FALSE
|
|
|
|
// Make sure the callbacks are setup.
|
|
var/list/callbacks = global_listeners[listener]
|
|
if (!callbacks)
|
|
callbacks = list()
|
|
global_listeners[listener] = callbacks
|
|
|
|
// Add the callback and return true.
|
|
callbacks |= proc_call
|
|
return TRUE
|
|
|
|
/decl/observ/proc/unregister_global(var/datum/listener, var/proc_call)
|
|
// Return false unless the listener is set as a global listener.
|
|
if (!(listener && (listener in global_listeners)))
|
|
return FALSE
|
|
|
|
// Remove all callbacks if no specific one is given.
|
|
if (!proc_call)
|
|
global_listeners -= listener
|
|
return TRUE
|
|
|
|
// See if the listener is registered.
|
|
var/list/callbacks = global_listeners[listener]
|
|
if (!callbacks)
|
|
return FALSE
|
|
|
|
// See if the callback exists.
|
|
if(!callbacks.Remove(proc_call))
|
|
return FALSE
|
|
|
|
if (!callbacks.len)
|
|
global_listeners -= listener
|
|
return TRUE
|
|
|
|
/decl/observ/proc/raise_event()
|
|
// Sanity
|
|
if (!args.len)
|
|
return FALSE
|
|
|
|
// Call the global listeners.
|
|
for (var/datum/listener in global_listeners)
|
|
var/list/callbacks = global_listeners[listener]
|
|
for (var/proc_call in callbacks)
|
|
|
|
// If the callback crashes, record the error and remove it.
|
|
try
|
|
call(listener, proc_call)(arglist(args))
|
|
catch (var/exception/e)
|
|
error("[e.name] - [e.file] - [e.line]")
|
|
error(e.desc)
|
|
unregister_global(listener, proc_call)
|
|
|
|
// Call the listeners for this specific event source, if they exist.
|
|
var/source = args[1]
|
|
if (source in event_sources)
|
|
var/list/listeners = event_sources[source]
|
|
for (var/datum/listener in listeners)
|
|
var/list/callbacks = listeners[listener]
|
|
for (var/proc_call in callbacks)
|
|
|
|
// If the callback crashes, record the error and remove it.
|
|
try
|
|
call(listener, proc_call)(arglist(args))
|
|
catch (var/exception/e)
|
|
error("[e.name] - [e.file] - [e.line]")
|
|
error(e.desc)
|
|
unregister(source, listener, proc_call)
|
|
|
|
return TRUE
|