From 6152eb96b33150c972682ffcf593ca2d319bc3f1 Mon Sep 17 00:00:00 2001 From: Watermelon914 <37270891+Watermelon914@users.noreply.github.com> Date: Mon, 6 Sep 2021 12:06:20 +0100 Subject: [PATCH] Adds instant integrated circuit execution for event handling circuits. (#61205) --- code/__DEFINES/dcs/signals.dm | 5 ++ code/__DEFINES/wiremod.dm | 5 ++ .../subsystem/circuit_component.dm | 74 +++++++++++++++++++ code/controllers/subsystem/delay_component.dm | 35 --------- code/modules/jobs/access.dm | 28 +++---- code/modules/wiremod/core/component.dm | 18 ++++- .../wiremod/core/integrated_circuit.dm | 3 +- code/modules/wiremod/core/port.dm | 6 +- code/modules/wiremod/shell/airlock.dm | 66 ++++++++++++++++- tgstation.dme | 2 +- 10 files changed, 183 insertions(+), 59 deletions(-) create mode 100644 code/controllers/subsystem/circuit_component.dm delete mode 100644 code/controllers/subsystem/delay_component.dm diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 72280f0bc74..5862454162a 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -808,6 +808,11 @@ /// from /obj/machinery/atmospherics/components/binary/valve/toggle(): (on) #define COMSIG_VALVE_SET_OPEN "valve_toggled" +// /obj access signals + +#define COMSIG_OBJ_ALLOWED "door_try_to_activate" + #define COMPONENT_OBJ_ALLOW (1<<0) + // /obj/machinery/door/airlock signals //from /obj/machinery/door/airlock/open(): (forced) diff --git a/code/__DEFINES/wiremod.dm b/code/__DEFINES/wiremod.dm index 90d5cb24a83..5bef0a0c363 100644 --- a/code/__DEFINES/wiremod.dm +++ b/code/__DEFINES/wiremod.dm @@ -1,6 +1,9 @@ /// Helper define that can only be used in /obj/item/circuit_component/input_received() #define COMPONENT_TRIGGERED_BY(trigger, port) (trigger.value && trigger == port) +/// Define to be placed at any proc that is triggered by a port. +#define CIRCUIT_TRIGGER SHOULD_NOT_SLEEP(TRUE) + // Port defines #define PORT_MAX_NAME_LENGTH 50 @@ -95,6 +98,8 @@ #define CIRCUIT_FLAG_ADMIN (1<<3) /// This circuit component does not show in the menu. #define CIRCUIT_FLAG_HIDDEN (1<<4) +/// This circuit component has been marked as a component that has instant execution and will show up in the UI as so. This will only cause a visual change. +#define CIRCUIT_FLAG_INSTANT (1<<5) // Datatype flags /// The datatype supports manual inputs diff --git a/code/controllers/subsystem/circuit_component.dm b/code/controllers/subsystem/circuit_component.dm new file mode 100644 index 00000000000..0ee631071e9 --- /dev/null +++ b/code/controllers/subsystem/circuit_component.dm @@ -0,0 +1,74 @@ +SUBSYSTEM_DEF(circuit_component) + name = "Circuit Components" + wait = 0.1 SECONDS + priority = FIRE_PRIORITY_DEFAULT + + var/list/callbacks_to_invoke = list() + var/list/currentrun = list() + + var/instant_run_tick = 0 + var/instant_run_start_cpu_usage = 0 + var/instant_run_max_cpu_usage = 10 + var/list/instant_run_callbacks_to_run = list() + +/datum/controller/subsystem/circuit_component/fire(resumed) + if(!resumed) + currentrun = callbacks_to_invoke.Copy() + callbacks_to_invoke.Cut() + + while(length(currentrun)) + var/datum/callback/to_call = currentrun[1] + currentrun.Cut(1,2) + + if(QDELETED(to_call)) + continue + + to_call.user = null + to_call.InvokeAsync() + qdel(to_call) + + + if(MC_TICK_CHECK) + return + +/** + * Adds a callback to be invoked when the next fire() is done. Used by the integrated circuit system. + * + * Prevents race conditions as it acts like a queue system. + * Those that registered first will be executed first and those registered last will be executed last. + */ +/datum/controller/subsystem/circuit_component/proc/add_callback(datum/port/input, datum/callback/to_call) + if(instant_run_tick == world.time && (TICK_USAGE - instant_run_start_cpu_usage) < instant_run_max_cpu_usage) + instant_run_callbacks_to_run += to_call + return + + callbacks_to_invoke += to_call + +/// Queues any callbacks to be executed instantly instead of using the subsystem. +/datum/controller/subsystem/circuit_component/proc/queue_instant_run() + instant_run_tick = world.time + instant_run_start_cpu_usage = TICK_USAGE + instant_run_callbacks_to_run = list() + +/** + * Instantly executes the stored callbacks and does this in a loop until there are no stored callbacks or it hits tick limit. + * + * Returns a list containing any values added by any input port. + */ +/datum/controller/subsystem/circuit_component/proc/execute_instant_run() + var/list/received_inputs = list() + while(length(instant_run_callbacks_to_run)) + var/list/instant_run_currentrun = instant_run_callbacks_to_run + instant_run_callbacks_to_run = list() + while(length(instant_run_currentrun)) + var/datum/callback/to_call = instant_run_currentrun[1] + instant_run_currentrun.Cut(1,2) + to_call.user = null + to_call.InvokeAsync(received_inputs) + qdel(to_call) + + instant_run_tick = 0 + if((TICK_USAGE - instant_run_start_cpu_usage) < instant_run_max_cpu_usage) + return received_inputs + else + return null diff --git a/code/controllers/subsystem/delay_component.dm b/code/controllers/subsystem/delay_component.dm deleted file mode 100644 index 85884f7dbe7..00000000000 --- a/code/controllers/subsystem/delay_component.dm +++ /dev/null @@ -1,35 +0,0 @@ -SUBSYSTEM_DEF(circuit_component) - name = "Circuit Components" - wait = 0.1 SECONDS - priority = FIRE_PRIORITY_DEFAULT - - var/list/callbacks_to_invoke = list() - var/list/currentrun = list() - -/datum/controller/subsystem/circuit_component/fire(resumed) - if(!resumed) - currentrun = callbacks_to_invoke.Copy() - callbacks_to_invoke.Cut() - - while(length(currentrun)) - var/datum/callback/to_call = currentrun[1] - currentrun.Cut(1,2) - - if(QDELETED(to_call)) - continue - - to_call.user = null - to_call.InvokeAsync() - qdel(to_call) - - if(MC_TICK_CHECK) - return - -/** - * Adds a callback to be invoked when the next fire() is done. Used by the integrated circuit system. - * - * Prevents race conditions as it acts like a queue system. - * Those that registered first will be executed first and those registered last will be executed last. - */ -/datum/controller/subsystem/circuit_component/proc/add_callback(datum/callback/to_call) - callbacks_to_invoke += to_call diff --git a/code/modules/jobs/access.dm b/code/modules/jobs/access.dm index 2b6e4141c92..8ed2aa18358 100644 --- a/code/modules/jobs/access.dm +++ b/code/modules/jobs/access.dm @@ -1,31 +1,33 @@ //returns TRUE if this mob has sufficient access to use this object -/obj/proc/allowed(mob/M) +/obj/proc/allowed(mob/accessor) + if(SEND_SIGNAL(src, COMSIG_OBJ_ALLOWED, accessor) & COMPONENT_OBJ_ALLOW) + return TRUE //check if it doesn't require any access at all if(src.check_access(null)) return TRUE - if(issilicon(M)) - if(ispAI(M)) + if(issilicon(accessor)) + if(ispAI(accessor)) return FALSE return TRUE //AI can do whatever it wants - if(isAdminGhostAI(M)) + if(isAdminGhostAI(accessor)) //Access can't stop the abuse return TRUE - else if(istype(M) && SEND_SIGNAL(M, COMSIG_MOB_ALLOWED, src)) + else if(istype(accessor) && SEND_SIGNAL(accessor, COMSIG_MOB_ALLOWED, src)) return TRUE - else if(ishuman(M)) - var/mob/living/carbon/human/H = M + else if(ishuman(accessor)) + var/mob/living/carbon/human/human_accessor = accessor //if they are holding or wearing a card that has access, that works - if(check_access(H.get_active_held_item()) || src.check_access(H.wear_id)) + if(check_access(human_accessor.get_active_held_item()) || src.check_access(human_accessor.wear_id)) return TRUE - else if(isalienadult(M)) - var/mob/living/carbon/george = M + else if(isalienadult(accessor)) + var/mob/living/carbon/george = accessor //they can only hold things :( if(check_access(george.get_active_held_item())) return TRUE - else if(isanimal(M)) - var/mob/living/simple_animal/A = M - if(check_access(A.get_active_held_item()) || check_access(A.access_card)) + else if(isanimal(accessor)) + var/mob/living/simple_animal/animal = accessor + if(check_access(animal.get_active_held_item()) || check_access(animal.access_card)) return TRUE return FALSE diff --git a/code/modules/wiremod/core/component.dm b/code/modules/wiremod/core/component.dm index c4b1a159eb0..983a26c4224 100644 --- a/code/modules/wiremod/core/component.dm +++ b/code/modules/wiremod/core/component.dm @@ -16,6 +16,9 @@ /// The name of the component shown on the UI var/display_name = "Generic" + /// The colour this circuit component appears in the UI + var/ui_color = "blue" + /// The integrated_circuit that this component is attached to. var/obj/item/integrated_circuit/parent @@ -70,6 +73,8 @@ trigger_input = add_input_port("Trigger", PORT_TYPE_SIGNAL, order = 2) if(circuit_flags & CIRCUIT_FLAG_OUTPUT_SIGNAL) trigger_output = add_output_port("Triggered", PORT_TYPE_SIGNAL, order = 2) + if(circuit_flags & CIRCUIT_FLAG_INSTANT) + ui_color = "orange" /obj/item/circuit_component/Destroy() @@ -184,8 +189,9 @@ * Return value indicates whether the trigger was successful or not. * Arguments: * * port - Can be null. The port that sent the input + * * return_values - Only defined if the component is receiving an input due to instant execution. Contains the values to be returned once execution has stopped. */ -/obj/item/circuit_component/proc/trigger_component(datum/port/input/port) +/obj/item/circuit_component/proc/trigger_component(datum/port/input/port, list/return_values) SHOULD_NOT_SLEEP(TRUE) pre_input_received(port) if(!should_receive_input(port)) @@ -196,9 +202,9 @@ var/proc_to_call = port.trigger if(!proc_to_call) return FALSE - result = call(src, proc_to_call)(port) + result = call(src, proc_to_call)(port, return_values) else - result = input_received() + result = input_received(null, return_values) if(result) return FALSE @@ -248,8 +254,9 @@ * Return value indicates that the circuit should not send an output signal. * Arguments: * * port - Can be null. The port that sent the input + * * return_values - Only defined if the component is receiving an input due to instant execution. Contains the values to be returned once execution has stopped. */ -/obj/item/circuit_component/proc/input_received(datum/port/input/port) +/obj/item/circuit_component/proc/input_received(datum/port/input/port, list/return_values) SHOULD_NOT_SLEEP(TRUE) return @@ -274,6 +281,9 @@ /obj/item/circuit_component/proc/get_ui_notices() . = list() + if(circuit_flags & CIRCUIT_FLAG_INSTANT) + . += create_ui_notice("Instant Execution", "red", "tachometer-alt") + if(!removable) . += create_ui_notice("Unremovable", "red", "lock") diff --git a/code/modules/wiremod/core/integrated_circuit.dm b/code/modules/wiremod/core/integrated_circuit.dm index ba2293be839..0bad23e503c 100644 --- a/code/modules/wiremod/core/integrated_circuit.dm +++ b/code/modules/wiremod/core/integrated_circuit.dm @@ -278,7 +278,7 @@ GLOBAL_LIST_EMPTY_TYPED(integrated_circuits, /obj/item/integrated_circuit) "connected_to" = connected_to, "color" = port.color, "current_data" = current_data, - "datatype_data" = port.datatype_ui_data(user), + "datatype_data" = port.datatype_ui_data(user) )) component_data["output_ports"] = list() for(var/datum/port/output/port as anything in component.output_ports) @@ -293,6 +293,7 @@ GLOBAL_LIST_EMPTY_TYPED(integrated_circuits, /obj/item/integrated_circuit) component_data["x"] = component.rel_x component_data["y"] = component.rel_y component_data["removable"] = component.removable + component_data["color"] = component.ui_color .["components"] += list(component_data) .["variables"] = list() diff --git a/code/modules/wiremod/core/port.dm b/code/modules/wiremod/core/port.dm index 732dda9efd3..aeabf886766 100644 --- a/code/modules/wiremod/core/port.dm +++ b/code/modules/wiremod/core/port.dm @@ -57,12 +57,12 @@ /** * Updates the value of the input and calls input_received on the connected component */ -/datum/port/input/proc/set_input(value) +/datum/port/input/proc/set_input(value, list/return_values) if(QDELETED(src)) //Pain return set_value(value) if(trigger) - connected_component.trigger_component(src) + connected_component.trigger_component(src, return_values) /datum/port/output/proc/set_output(value) set_value(value) @@ -204,7 +204,7 @@ */ /datum/port/input/proc/receive_value(datum/port/output/output, value) SIGNAL_HANDLER - SScircuit_component.add_callback(CALLBACK(src, .proc/set_input, value)) + SScircuit_component.add_callback(src, CALLBACK(src, .proc/set_input, value)) /// Signal handler proc to null the input if an atom is deleted. An update is not sent because this was not set by anything. /datum/port/proc/null_value(datum/source) diff --git a/code/modules/wiremod/shell/airlock.dm b/code/modules/wiremod/shell/airlock.dm index 34e6cf7b20f..c597182a572 100644 --- a/code/modules/wiremod/shell/airlock.dm +++ b/code/modules/wiremod/shell/airlock.dm @@ -16,7 +16,7 @@ . = ..() AddComponent( \ /datum/component/shell, \ - unremovable_circuit_components = list(new /obj/item/circuit_component/airlock), \ + unremovable_circuit_components = list(new /obj/item/circuit_component/airlock, new /obj/item/circuit_component/airlock_access_event), \ capacity = SHELL_CAPACITY_LARGE, \ shell_flags = SHELL_FLAG_ALLOW_FAILURE_ACTION \ ) @@ -37,7 +37,7 @@ display_name = "Airlock" desc = "The general interface with an airlock. Includes general statuses of the airlock" - /// Called when attack_hand is called on the shell. + /// The shell, if it is an airlock. var/obj/machinery/door/airlock/attached_airlock /// Bolts the airlock (if possible) @@ -127,3 +127,65 @@ INVOKE_ASYNC(attached_airlock, /obj/machinery/door/airlock.proc/open) if(COMPONENT_TRIGGERED_BY(close, port) && !attached_airlock.density) INVOKE_ASYNC(attached_airlock, /obj/machinery/door/airlock.proc/close) + + +/obj/item/circuit_component/airlock_access_event + display_name = "Airlock Access Event" + desc = "An event that can be handled through circuit components to determine if the door should open or not for an entity that might be trying to access it." + circuit_flags = CIRCUIT_FLAG_INSTANT + + /// The shell, if it is an airlock. + var/obj/machinery/door/airlock/attached_airlock + + /// Tells the event to open the airlock. + var/datum/port/input/open_airlock + + /// The person trying to open the airlock. + var/datum/port/output/accessing_entity + + /// The signal sent when this event is triggered + var/datum/port/output/event_triggered + + +/obj/item/circuit_component/airlock_access_event/register_shell(atom/movable/shell) + . = ..() + if(istype(shell, /obj/machinery/door/airlock)) + attached_airlock = shell + RegisterSignal(shell, COMSIG_OBJ_ALLOWED, .proc/handle_allowed) + +/obj/item/circuit_component/airlock_access_event/unregister_shell(atom/movable/shell) + attached_airlock = null + UnregisterSignal(shell, list( + COMSIG_OBJ_ALLOWED, + )) + return ..() + + +/obj/item/circuit_component/airlock_access_event/populate_ports() + open_airlock = add_input_port("Should Open Airlock", PORT_TYPE_SIGNAL, trigger = .proc/should_open_airlock) + accessing_entity = add_output_port("Accessing Entity", PORT_TYPE_ATOM) + event_triggered = add_output_port("Event Triggered", PORT_TYPE_SIGNAL) + + +/obj/item/circuit_component/airlock_access_event/proc/should_open_airlock(datum/port/input/port, list/return_values) + CIRCUIT_TRIGGER + if(!return_values) + return + return_values["should_open"] = TRUE + +/obj/item/circuit_component/airlock_access_event/proc/handle_allowed(datum/source, mob/accesser) + SIGNAL_HANDLER + if(!attached_airlock) + return + + SScircuit_component.queue_instant_run() + accessing_entity.set_output(accesser) + event_triggered.set_output(COMPONENT_SIGNAL) + var/list/result = SScircuit_component.execute_instant_run() + + if(!result) + attached_airlock.visible_message(span_warning("[attached_airlock]'s circuitry overheats!")) + return + + if(result["should_open"]) + return COMPONENT_OBJ_ALLOW diff --git a/tgstation.dme b/tgstation.dme index c10c9530e05..6b9cc71288e 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -324,10 +324,10 @@ #include "code\controllers\subsystem\blackbox.dm" #include "code\controllers\subsystem\blackmarket.dm" #include "code\controllers\subsystem\chat.dm" +#include "code\controllers\subsystem\circuit_component.dm" #include "code\controllers\subsystem\communications.dm" #include "code\controllers\subsystem\dbcore.dm" #include "code\controllers\subsystem\dcs.dm" -#include "code\controllers\subsystem\delay_component.dm" #include "code\controllers\subsystem\discord.dm" #include "code\controllers\subsystem\disease.dm" #include "code\controllers\subsystem\economy.dm"