[MIRROR] Circuit submodules (#6980)

* Circuit submodules (#60109)

Adds the module component that is basically a subroutine. Allows you to compact your logic into a bunch of functions.

* Circuit submodules

Co-authored-by: Watermelon914 <37270891+Watermelon914@users.noreply.github.com>
This commit is contained in:
SkyratBot
2021-07-19 10:12:15 +02:00
committed by GitHub
parent c3d3637fdb
commit b37a8b5ef0
12 changed files with 480 additions and 18 deletions

View File

@@ -1344,6 +1344,15 @@
/// Sent to [/obj/item/circuit_component] when it is removed from a circuit. (/obj/item/integrated_circuit)
#define COMSIG_CIRCUIT_COMPONENT_REMOVED "circuit_component_removed"
/// Called when the integrated circuit's cell is set.
#define COMSIG_CIRCUIT_SET_CELL "circuit_set_cell"
/// Called when the integrated circuit is turned on or off.
#define COMSIG_CIRCUIT_SET_ON "circuit_set_on"
/// Called when the integrated circuit's shell is set.
#define COMSIG_CIRCUIT_SET_SHELL "circuit_set_shell"
/// Sent to an atom when a [/obj/item/usb_cable] attempts to connect to something. (/obj/item/usb_cable/usb_cable, /mob/user)
#define COMSIG_ATOM_USB_CABLE_TRY_ATTACH "usb_cable_try_attach"
/// Attaches the USB cable to the atom. If the USB cables moves away, it will disconnect.

View File

@@ -4,6 +4,10 @@
/// Define to automatically handle calling the output port. Will not call the output port if the input_received proc returns TRUE.
#define TRIGGER_CIRCUIT_COMPONENT(component, port) if(!component.input_received(port) && (component.circuit_flags & CIRCUIT_FLAG_OUTPUT_SIGNAL)) component.trigger_output.set_output(COMPONENT_SIGNAL)
// Port defines
#define PORT_MAX_NAME_LENGTH 50
// Port types. Determines what the port can connect to
/// Can accept any datatype. Only works for inputs, output types will runtime.

View File

@@ -0,0 +1,9 @@
GLOBAL_LIST_INIT(wiremod_types, list(
PORT_TYPE_ANY,
PORT_TYPE_STRING,
PORT_TYPE_NUMBER,
PORT_TYPE_SIGNAL,
PORT_TYPE_LIST,
PORT_TYPE_TABLE,
PORT_TYPE_ATOM,
))

View File

@@ -196,7 +196,8 @@
* Attaches a circuit to the parent. Doesn't do any checks to see for any existing circuits so that should be done beforehand.
*/
/datum/component/shell/proc/attach_circuit(obj/item/integrated_circuit/circuitboard, mob/living/user)
if(!user.transferItemToLoc(circuitboard, parent))
var/atom/movable/parent_atom = parent
if(!user.transferItemToLoc(circuitboard, parent_atom))
return
locked = FALSE
attached_circuit = circuitboard
@@ -206,11 +207,12 @@
to_add.forceMove(attached_circuit)
attached_circuit.add_component(to_add)
RegisterSignal(circuitboard, COMSIG_CIRCUIT_ADD_COMPONENT_MANUALLY, .proc/on_circuit_add_component_manually)
attached_circuit.set_shell(parent)
attached_circuit.set_shell(parent_atom)
if(attached_circuit.display_name != "")
parent_atom.name = "[initial(parent_atom.name)] ([attached_circuit.display_name])"
attached_circuit.locked = FALSE
if(shell_flags & SHELL_FLAG_REQUIRE_ANCHOR)
var/atom/movable/parent_atom = parent
on_unfasten(parent_atom, parent_atom.anchored)
/**

View File

@@ -214,15 +214,21 @@
build_path = /obj/item/circuit_component/select
/datum/design/component/tempsensor
name = "Temperature Sensor"
name = "Temperature Sensor Component"
id = "comp_tempsensor"
build_path = /obj/item/circuit_component/tempsensor
/datum/design/component/pressuresensor
name = "Pressure Sensor"
name = "Pressure Sensor Component"
id = "comp_pressuresensor"
build_path = /obj/item/circuit_component/pressuresensor
/datum/design/component/module
name = "Module Component"
id = "comp_module"
build_path = /obj/item/circuit_component/module
/datum/design/compact_remote_shell
name = "Compact Remote Shell"
desc = "A handheld shell with one big button."

View File

@@ -228,6 +228,7 @@
"comp_light",
"comp_logic",
"comp_mmi",
"comp_module",
"comp_multiplexer",
"comp_not",
"comp_pressuresensor",

View File

@@ -142,8 +142,21 @@
/obj/item/circuit_component/proc/add_input_port(name, type, trigger = TRUE, default = null)
var/datum/port/input/input_port = new(src, name, type, trigger, default)
input_ports += input_port
if(parent)
SStgui.update_uis(parent)
return input_port
/**
* Removes an input port and deletes it. This will not cleanup any references made by derivatives of the circuit component
*
* Arguments:
* * input_port - The input port to remove.
*/
/obj/item/circuit_component/proc/remove_input_port(datum/port/input/input_port)
input_ports -= input_port
qdel(input_port)
if(parent)
SStgui.update_uis(parent)
/**
* Adds an output port and returns it
@@ -157,6 +170,18 @@
output_ports += output_port
return output_port
/**
* Removes an output port and deletes it. This will not cleanup any references made by derivatives of the circuit component
*
* Arguments:
* * output_port - The output port to remove.
*/
/obj/item/circuit_component/proc/remove_output_port(datum/port/output/output_port)
output_ports -= output_port
qdel(output_port)
if(parent)
SStgui.update_uis(parent)
/**
* Called whenever an input is received from one of the ports.
*

View File

@@ -0,0 +1,252 @@
/**
* # Module Component
*
* A component that has an input, output
*/
/obj/item/circuit_component/module
display_name = "Module"
display_desc = "A component that has other components within it, acting like a function. Use it in your hand to control the amount of input and output ports it has, as well as being able to access the integrated circuit contained inside."
var/obj/item/integrated_circuit/module/internal_circuit
var/obj/item/circuit_component/module_input/input_component
var/obj/item/circuit_component/module_output/output_component
/// Linked ports that follow a `first_port = second_port` keyed structure.
var/list/linked_ports = list()
var/port_limit = 10
/obj/item/integrated_circuit/module
var/obj/item/circuit_component/attached_module
/obj/item/integrated_circuit/module/ui_host(mob/user)
. = ..()
if(. == src)
return attached_module
/obj/item/integrated_circuit/module/set_display_name(new_name)
. = ..()
attached_module.display_name = new_name
/obj/item/integrated_circuit/module/Destroy()
attached_module = null
return ..()
/obj/item/circuit_component/module_input
display_name = "Input"
display_desc = "A component that receives data from the module it is attached to"
removable = FALSE
/// The currently attached module
var/obj/item/circuit_component/module/attached_module
/obj/item/circuit_component/module_input/Destroy()
attached_module = null
return ..()
/obj/item/circuit_component/module_output
display_name = "Output"
display_desc = "A component that outputs data to the module it is attached to."
removable = FALSE
/// The currently attached module
var/obj/item/circuit_component/module/attached_module
/obj/item/circuit_component/module_output/input_received(datum/port/input/port)
. = ..()
if(!port)
return
// We don't check the parent here because frankly, we don't care. We only sync our input with the module's output
var/datum/port/output/port_to_update = attached_module.linked_ports[port]
if(!port_to_update)
CRASH("[port.type] doesn't have a linked port in [type]!")
port_to_update.set_output(port.input_value)
/obj/item/circuit_component/module/input_received(datum/port/input/port)
. = ..()
if(!port)
return
var/datum/port/output/port_to_update = linked_ports[port]
if(!port_to_update)
CRASH("[port.type] doesn't have a linked port in [type]!")
port_to_update.set_output(port.input_value)
/obj/item/circuit_component/module_output/Destroy()
attached_module = null
return ..()
/obj/item/circuit_component/module/Initialize()
. = ..()
internal_circuit = new(src)
internal_circuit.attached_module = src
input_component = new(internal_circuit)
input_component.attached_module = src
internal_circuit.add_component(input_component)
input_component.rel_x = 400
input_component.rel_y = 200
output_component = new(internal_circuit)
output_component.attached_module = src
internal_circuit.add_component(output_component)
output_component.rel_x = 400
output_component.rel_y = 200
/obj/item/circuit_component/module/add_to(obj/item/integrated_circuit/added_to)
. = ..()
RegisterSignal(added_to, COMSIG_CIRCUIT_SET_CELL, .proc/handle_set_cell)
RegisterSignal(added_to, COMSIG_CIRCUIT_SET_ON, .proc/handle_set_on)
RegisterSignal(added_to, COMSIG_CIRCUIT_SET_SHELL, .proc/handle_set_shell)
internal_circuit.set_cell(added_to.cell)
internal_circuit.set_shell(added_to.shell)
internal_circuit.set_on(added_to.on)
/obj/item/circuit_component/module/removed_from(obj/item/integrated_circuit/removed_from)
internal_circuit.set_cell(null)
internal_circuit.set_on(FALSE)
internal_circuit.remove_current_shell()
UnregisterSignal(removed_from, list(
COMSIG_CIRCUIT_SET_CELL,
COMSIG_CIRCUIT_SET_ON,
COMSIG_CIRCUIT_SET_SHELL,
))
return ..()
/obj/item/circuit_component/module/proc/handle_set_cell(datum/source, obj/item/stock_parts/cell/cell)
SIGNAL_HANDLER
internal_circuit.set_cell(cell)
/obj/item/circuit_component/module/proc/handle_set_on(datum/source, new_value)
SIGNAL_HANDLER
internal_circuit.set_on(new_value)
/obj/item/circuit_component/module/proc/handle_set_shell(datum/source, atom/movable/new_shell)
SIGNAL_HANDLER
internal_circuit.set_shell(new_shell)
/obj/item/circuit_component/module/Destroy()
QDEL_NULL(input_component)
QDEL_NULL(output_component)
QDEL_NULL(internal_circuit)
linked_ports = null
return ..()
/obj/item/circuit_component/module/ui_data(mob/user)
. = list()
.["input_ports"] = list()
for(var/datum/port/input/input_port as anything in input_ports)
.["input_ports"] += list(list(
"name" = input_port.name,
"type" = input_port.datatype,
))
.["output_ports"] = list()
for(var/datum/port/output/output_port as anything in output_ports)
.["output_ports"] += list(list(
"name" = output_port.name,
"type" = output_port.datatype,
))
/obj/item/circuit_component/module/ui_static_data(mob/user)
. = list()
.["global_port_types"] = GLOB.wiremod_types
/obj/item/circuit_component/module/attackby(obj/item/I, mob/living/user, params)
if(istype(I, /obj/item/circuit_component))
internal_circuit.attackby(I, user, params)
return
return ..()
#define WITHIN_RANGE(id, table) (id >= 1 && id <= length(table))
/obj/item/circuit_component/module/ui_act(action, list/params)
. = ..()
if(.)
return
switch(action)
if("open_internal_circuit")
internal_circuit.interact(usr)
. = TRUE
if("add_input_port")
if(length(input_ports) > port_limit)
return
var/datum/port/new_port = add_input_port("Input Port", PORT_TYPE_ANY)
linked_ports[new_port] = input_component.add_output_port("Input Port", PORT_TYPE_ANY)
. = TRUE
if("remove_input_port")
var/port_id = text2num(params["port_id"])
if(!WITHIN_RANGE(port_id, input_ports))
return
var/datum/port/removed_port = input_ports[port_id]
linked_ports -= removed_port
remove_input_port(removed_port)
input_component.remove_output_port(input_component.output_ports[port_id])
. = TRUE
if("add_output_port")
if(length(output_ports) > port_limit)
return
var/datum/port/new_port = output_component.add_input_port("Output Port", PORT_TYPE_ANY)
linked_ports[new_port] = add_output_port("Output Port", PORT_TYPE_ANY)
. = TRUE
if("remove_output_port")
var/port_id = text2num(params["port_id"])
if(!WITHIN_RANGE(port_id, output_ports))
return
var/datum/port/removed_port = output_component.input_ports[port_id]
linked_ports -= removed_port
remove_output_port(output_ports[port_id])
output_component.remove_input_port(removed_port)
. = TRUE
if("set_port_name", "set_port_type")
var/port_id = text2num(params["port_id"])
var/is_input = params["is_input"]
var/list/ports_to_use
var/list/internal_ports_to_use
if(is_input)
ports_to_use = input_ports
internal_ports_to_use = input_component.output_ports
else
ports_to_use = output_ports
internal_ports_to_use = output_component.input_ports
if(!WITHIN_RANGE(port_id, ports_to_use))
return
var/datum/port/component_port = ports_to_use[port_id]
var/datum/port/internal_component_port = internal_ports_to_use[port_id]
if(action == "set_port_type")
var/type = params["port_type"]
if(!(type in GLOB.wiremod_types))
return
component_port.set_datatype(type)
internal_component_port.set_datatype(type)
else
var/port_name = params["port_name"]
if(!port_name)
return
port_name = strip_html(port_name, PORT_MAX_NAME_LENGTH)
component_port.name = port_name
internal_component_port.name = port_name
. = TRUE
if(.)
SStgui.update_uis(internal_circuit)
#undef WITHIN_RANGE
/obj/item/circuit_component/module/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "CircuitModule", name)
ui.open()
ui.set_autoupdate(FALSE)

View File

@@ -53,7 +53,7 @@
/obj/item/integrated_circuit/loaded/Initialize()
. = ..()
cell = new /obj/item/stock_parts/cell/high(src)
set_cell(new /obj/item/stock_parts/cell/high(src))
/obj/item/integrated_circuit/Destroy()
for(var/obj/item/circuit_component/to_delete in attached_components)
@@ -73,6 +73,10 @@
else
. += span_notice("There is no power cell installed.")
/obj/item/integrated_circuit/proc/set_cell(obj/item/stock_parts/cell_to_set)
SEND_SIGNAL(src, COMSIG_CIRCUIT_SET_CELL, cell_to_set)
cell = cell_to_set
/obj/item/integrated_circuit/attackby(obj/item/I, mob/living/user, params)
. = ..()
if(istype(I, /obj/item/circuit_component))
@@ -85,7 +89,7 @@
return
if(!user.transferItemToLoc(I, src))
return
cell = I
set_cell(I)
I.add_fingerprint(user)
user.visible_message(span_notice("[user] inserts a power cell into [src]."), span_notice("You insert the power cell into [src]."))
return
@@ -101,7 +105,7 @@
I.play_tool_sound(src)
user.visible_message(span_notice("[user] unscrews the power cell from [src]."), span_notice("You unscrew the power cell from [src]."))
cell.forceMove(drop_location())
cell = null
set_cell(null)
return
/**
@@ -114,7 +118,8 @@
*/
/obj/item/integrated_circuit/proc/set_shell(atom/movable/new_shell)
remove_current_shell()
on = TRUE
set_on(TRUE)
SEND_SIGNAL(src, COMSIG_CIRCUIT_SET_SHELL, new_shell)
shell = new_shell
RegisterSignal(shell, COMSIG_PARENT_QDELETING, .proc/remove_current_shell)
for(var/obj/item/circuit_component/attached_component as anything in attached_components)
@@ -122,8 +127,6 @@
// Their input ports may be updated with user values, but the outputs haven't updated
// because on is FALSE
TRIGGER_CIRCUIT_COMPONENT(attached_component, null)
if(display_name != "")
shell.name = "[initial(shell.name)] ([display_name])"
/**
* Unregisters the current shell attached to this circuit.
@@ -137,9 +140,13 @@
attached_component.unregister_shell(shell)
UnregisterSignal(shell, COMSIG_PARENT_QDELETING)
shell = null
on = FALSE
set_on(FALSE)
SEND_SIGNAL(src, COMSIG_CIRCUIT_SHELL_REMOVED)
/obj/item/integrated_circuit/proc/set_on(new_value)
SEND_SIGNAL(src, COMSIG_CIRCUIT_SET_ON, new_value)
on = new_value
/**
* Adds a component to the circuitboard
*
@@ -428,9 +435,9 @@
var/new_name = params["display_name"]
if(new_name)
display_name = strip_html(params["display_name"], label_max_length)
set_display_name(strip_html(params["display_name"], label_max_length))
else
display_name = ""
set_display_name("")
if(shell)
if(display_name != "")
@@ -458,6 +465,10 @@
#undef WITHIN_RANGE
/// Sets the display name that appears on the shell.
/obj/item/integrated_circuit/proc/set_display_name(new_name)
display_name = new_name
/**
* Returns the creator of the integrated circuit. Used in admin messages and other related things.
*/

View File

@@ -52,10 +52,6 @@
return "grey"
/datum/port/Destroy(force)
if(!force && !QDELETED(connected_component))
// This should never happen. Ports should be deleted with their components
stack_trace("Attempted to delete a port with a non-destroyed connected_component! (port name: [name], component type: [connected_component.type])")
return QDEL_HINT_LETMELIVE
connected_component = null
return ..()

View File

@@ -275,6 +275,7 @@
#include "code\_globalvars\lists\objects.dm"
#include "code\_globalvars\lists\poll_ignore.dm"
#include "code\_globalvars\lists\typecache.dm"
#include "code\_globalvars\lists\wiremod.dm"
#include "code\_globalvars\lists\xenobiology.dm"
#include "code\_js\byjax.dm"
#include "code\_js\menus.dm"
@@ -3621,6 +3622,7 @@
#include "code\modules\wiremod\port.dm"
#include "code\modules\wiremod\usb_cable.dm"
#include "code\modules\wiremod\components\abstract\compare.dm"
#include "code\modules\wiremod\components\abstract\module.dm"
#include "code\modules\wiremod\components\action\light.dm"
#include "code\modules\wiremod\components\action\mmi.dm"
#include "code\modules\wiremod\components\action\pull.dm"

View File

@@ -0,0 +1,145 @@
import { useBackend } from "../backend";
import { Stack, Section, Input, Button, Dropdown } from "../components";
import { Window } from "../layouts";
export const CircuitModule = (props, context) => {
const { act, data } = useBackend(context);
const {
input_ports,
output_ports,
global_port_types,
} = data;
return (
<Window width={600} height={300}>
<Window.Content scrollable>
<Stack vertical>
<Stack.Item>
<Button
content="View Internal Circuit"
textAlign="center"
fluid
onClick={() => act("open_internal_circuit")}
/>
</Stack.Item>
<Stack.Item>
<Stack width="100%">
<Stack.Item basis="50%">
<Section title="Input Ports">
<Stack vertical>
{input_ports.map((val, index) => (
<PortEntry
key={index}
name={val.name}
datatype={val.type}
datatypeOptions={global_port_types}
onRemove={() => act("remove_input_port", {
port_id: index+1,
})}
onSetType={type => act("set_port_type", {
port_id: index+1,
is_input: true,
port_type: type,
})}
onEnter={(e, value) => act("set_port_name", {
port_id: index+1,
is_input: true,
port_name: value,
})}
/>
))}
<Stack.Item>
<Button
fluid
content="Add Input Port"
color="good"
icon="plus"
onClick={() => act("add_input_port")}
/>
</Stack.Item>
</Stack>
</Section>
</Stack.Item>
<Stack.Item basis="50%">
<Section title="Output Ports">
<Stack vertical>
{output_ports.map((val, index) => (
<PortEntry
key={index}
name={val.name}
datatype={val.type}
datatypeOptions={global_port_types}
onRemove={() => act("remove_output_port", {
port_id: index+1,
})}
onSetType={type => act("set_port_type", {
port_id: index+1,
is_input: false,
port_type: type,
})}
onEnter={(e, value) => act("set_port_name", {
port_id: index+1,
is_input: false,
port_name: value,
})}
/>
))}
<Stack.Item>
<Button
fluid
content="Add Output Port"
color="good"
icon="plus"
onClick={() => act("add_output_port")}
/>
</Stack.Item>
</Stack>
</Section>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</Window.Content>
</Window>
);
};
const PortEntry = (props, context) => {
const {
onRemove,
onEnter,
onSetType,
name,
datatype,
datatypeOptions = [],
...rest
} = props;
return (
<Stack.Item {...rest}>
<Stack>
<Stack.Item grow>
<Input
placeholder="Name"
value={name}
onChange={onEnter}
fluid
/>
</Stack.Item>
<Stack.Item>
<Dropdown
displayText={datatype}
options={datatypeOptions}
onSelected={onSetType}
/>
</Stack.Item>
<Stack.Item>
<Button
icon="times"
color="red"
onClick={onRemove}
/>
</Stack.Item>
</Stack>
</Stack.Item>
);
};