<commit_before>/// Opens up a USB port that can be connected to by circuits, creating registerable circuit components
/datum/component/usb_port
	/// The component types to create when something plugs in
	var/list/circuit_component_types

	/// The currently connected circuit
	var/obj/item/integrated_circuit/attached_circuit

	/// The currently connected USB cable
	var/datum/weakref/usb_cable_ref

	/// The components inside the parent
	var/list/obj/item/circuit_component/circuit_components

	/// The beam connecting the USB cable to the machine
	var/datum/beam/usb_cable_beam

	/// The current physical object that the beam is connected to and listens to.
	var/atom/movable/physical_object

/datum/component/usb_port/Initialize(list/circuit_component_types)
	if (!isatom(parent))
		return COMPONENT_INCOMPATIBLE

	circuit_components = list()

	set_circuit_components(circuit_component_types)

/datum/component/usb_port/proc/set_circuit_components(list/components)
	var/should_register = FALSE
	if(length(circuit_components))
		UnregisterFromParent()
		should_register = TRUE
		QDEL_LIST(circuit_components)

	for(var/circuit_component in components)
		var/obj/item/circuit_component/component = circuit_component
		if(ispath(circuit_component))
			component = new circuit_component(null)
		RegisterSignal(component, COMSIG_CIRCUIT_COMPONENT_SAVE, .proc/save_component)
		circuit_components += component

	if(should_register)
		RegisterWithParent()

/datum/component/usb_port/RegisterWithParent()
	RegisterSignal(parent, COMSIG_ATOM_USB_CABLE_TRY_ATTACH, .proc/on_atom_usb_cable_try_attach)
	RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_moved)
	RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)
	RegisterSignal(parent, COMSIG_MOVABLE_CIRCUIT_LOADED, .proc/on_load)

	for(var/obj/item/circuit_component/component as anything in circuit_components)
		component.register_usb_parent(parent)

/datum/component/usb_port/UnregisterFromParent()
	UnregisterSignal(parent, list(
		COMSIG_ATOM_USB_CABLE_TRY_ATTACH,
		COMSIG_MOVABLE_MOVED,
		COMSIG_PARENT_EXAMINE,
		COMSIG_MOVABLE_CIRCUIT_LOADED,
	))

	for(var/obj/item/circuit_component/component as anything in circuit_components)
		component.unregister_usb_parent(parent)

	unregister_circuit_signals()
	unregister_physical_signals()
	attached_circuit = null

/datum/component/usb_port/proc/save_component(datum/source, list/objects)
	SIGNAL_HANDLER
	objects += parent

/datum/component/usb_port/proc/on_load(datum/source, obj/item/integrated_circuit/circuit, list/components)
	SIGNAL_HANDLER
	var/list/components_in_list = list()
	for(var/obj/item/circuit_component/component as anything in components)
		components_in_list += component.type

	for(var/obj/item/circuit_component/component as anything in circuit_components)
		if(component.type in components_in_list)
			continue
		components += component.type
	set_circuit_components(components)
	var/obj/item/usb_cable/cable = new(circuit.drop_location())
	cable.attached_circuit = circuit

	on_atom_usb_cable_try_attach(src, cable, null)

/datum/component/usb_port/Destroy()
	QDEL_LIST(circuit_components)
	QDEL_NULL(usb_cable_beam)

	attached_circuit = null
	usb_cable_ref = null

	return ..()

/datum/component/usb_port/proc/unregister_circuit_signals()
	if (isnull(attached_circuit))
		return

	UnregisterSignal(attached_circuit, list(
		COMSIG_CIRCUIT_SHELL_REMOVED,
		COMSIG_PARENT_QDELETING,
		COMSIG_CIRCUIT_SET_SHELL,
	))

/datum/component/usb_port/proc/unregister_physical_signals()
	if (isnull(physical_object))
		return

	UnregisterSignal(physical_object, list(
		COMSIG_MOVABLE_MOVED,
		COMSIG_PARENT_EXAMINE,
	))

/datum/component/usb_port/proc/attach_circuit_components(obj/item/integrated_circuit/circuitboard)
	for(var/obj/item/circuit_component/component as anything in circuit_components)
		circuitboard.add_component(component)
		RegisterSignal(component, COMSIG_CIRCUIT_COMPONENT_REMOVED, .proc/on_circuit_component_removed)

/datum/component/usb_port/proc/on_examine(datum/source, mob/user, list/examine_text)
	SIGNAL_HANDLER

	if (isnull(attached_circuit))
		examine_text += span_notice("There is a USB port on the front.")
	else
		examine_text += span_notice("[attached_circuit.shell || attached_circuit] is connected to [parent.p_them()] by a USB port.")

/datum/component/usb_port/proc/on_examine_shell(datum/source, mob/user, list/examine_text)
	SIGNAL_HANDLER

	examine_text += span_notice("[source.p_they(TRUE)] [source.p_are()] attached to [parent] with a USB cable.")

/datum/component/usb_port/proc/on_atom_usb_cable_try_attach(datum/source, obj/item/usb_cable/connecting_cable, mob/user)
	SIGNAL_HANDLER

	var/atom/atom_parent = parent

	if (!isnull(attached_circuit))
		if(user)
			atom_parent.balloon_alert(user, "usb already connected")
		return COMSIG_CANCEL_USB_CABLE_ATTACK

	if (isnull(connecting_cable.attached_circuit))
		if(user)
			connecting_cable.balloon_alert(user, "connect to a shell first")
		return COMSIG_CANCEL_USB_CABLE_ATTACK

	if (!IN_GIVEN_RANGE(connecting_cable.attached_circuit, parent, USB_CABLE_MAX_RANGE))
		if(user)
			connecting_cable.balloon_alert(user, "too far away")
		return COMSIG_CANCEL_USB_CABLE_ATTACK

	if (connecting_cable.attached_circuit.locked)
		connecting_cable.balloon_alert(user, "shell is locked!")
		return COMSIG_CANCEL_USB_CABLE_ATTACK
	
	usb_cable_ref = WEAKREF(connecting_cable)
	attached_circuit = connecting_cable.attached_circuit

	connecting_cable.forceMove(attached_circuit)
	attach_circuit_components(attached_circuit)
	if(user)
		attached_circuit.interact(user)

	var/new_physical_object = attached_circuit.shell
	if(!new_physical_object)
		new_physical_object = attached_circuit

	RegisterSignal(attached_circuit, COMSIG_CIRCUIT_SHELL_REMOVED, .proc/on_circuit_shell_removed)
	RegisterSignal(attached_circuit, COMSIG_PARENT_QDELETING, .proc/on_circuit_deleting)
	RegisterSignal(attached_circuit, COMSIG_CIRCUIT_SET_SHELL, .proc/on_set_shell)
	set_physical_object(new_physical_object)

	return COMSIG_USB_CABLE_ATTACHED

/datum/component/usb_port/proc/set_physical_object(atom/movable/new_physical_object)
	if(physical_object)
		unregister_physical_signals()
	if(usb_cable_beam)
		QDEL_NULL(usb_cable_beam)

	var/atom/atom_parent = parent<commit_msg>Load circuit components from USB ports on demand, saving 0.5s of init time (0.7s on prod) (#69664)<commit_after>/// Opens up a USB port that can be connected to by circuits, creating registerable circuit components
/datum/component/usb_port
	/// The component types to create when something plugs in
	var/list/circuit_component_types

	/// The currently connected circuit
	var/obj/item/integrated_circuit/attached_circuit

	/// The currently connected USB cable
	var/datum/weakref/usb_cable_ref

	/// The components inside the parent
	var/list/obj/item/circuit_component/circuit_components

	/// The beam connecting the USB cable to the machine
	var/datum/beam/usb_cable_beam

	/// The current physical object that the beam is connected to and listens to.
	var/atom/movable/physical_object

/datum/component/usb_port/Initialize(list/circuit_component_types)
	if (!isatom(parent))
		return COMPONENT_INCOMPATIBLE

	circuit_components = list()

	src.circuit_component_types = circuit_component_types

/datum/component/usb_port/proc/set_circuit_components(list/components)
	var/should_register = FALSE
	if(length(circuit_components))
		UnregisterFromParent()
		should_register = TRUE
		QDEL_LIST(circuit_components)

	for(var/circuit_component in components)
		var/obj/item/circuit_component/component = circuit_component
		if(ispath(circuit_component))
			component = new circuit_component(null)
		RegisterSignal(component, COMSIG_CIRCUIT_COMPONENT_SAVE, .proc/save_component)
		circuit_components += component

	if(should_register)
		RegisterWithParent()

/datum/component/usb_port/RegisterWithParent()
	RegisterSignal(parent, COMSIG_ATOM_USB_CABLE_TRY_ATTACH, .proc/on_atom_usb_cable_try_attach)
	RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_moved)
	RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)
	RegisterSignal(parent, COMSIG_MOVABLE_CIRCUIT_LOADED, .proc/on_load)

	for(var/obj/item/circuit_component/component as anything in circuit_components)
		component.register_usb_parent(parent)

/datum/component/usb_port/UnregisterFromParent()
	UnregisterSignal(parent, list(
		COMSIG_ATOM_USB_CABLE_TRY_ATTACH,
		COMSIG_MOVABLE_MOVED,
		COMSIG_PARENT_EXAMINE,
		COMSIG_MOVABLE_CIRCUIT_LOADED,
	))

	for(var/obj/item/circuit_component/component as anything in circuit_components)
		component.unregister_usb_parent(parent)

	unregister_circuit_signals()
	unregister_physical_signals()
	attached_circuit = null

/datum/component/usb_port/proc/save_component(datum/source, list/objects)
	SIGNAL_HANDLER
	objects += parent

/datum/component/usb_port/proc/on_load(datum/source, obj/item/integrated_circuit/circuit, list/components)
	SIGNAL_HANDLER
	var/list/components_in_list = list()
	for(var/obj/item/circuit_component/component as anything in components)
		components_in_list += component.type

	for(var/obj/item/circuit_component/component as anything in circuit_components)
		if(component.type in components_in_list)
			continue
		components += component.type
	set_circuit_components(components)
	var/obj/item/usb_cable/cable = new(circuit.drop_location())
	cable.attached_circuit = circuit

	on_atom_usb_cable_try_attach(src, cable, null)

/datum/component/usb_port/Destroy()
	QDEL_LIST(circuit_components)
	QDEL_NULL(usb_cable_beam)

	attached_circuit = null
	usb_cable_ref = null

	return ..()

/datum/component/usb_port/proc/unregister_circuit_signals()
	if (isnull(attached_circuit))
		return

	UnregisterSignal(attached_circuit, list(
		COMSIG_CIRCUIT_SHELL_REMOVED,
		COMSIG_PARENT_QDELETING,
		COMSIG_CIRCUIT_SET_SHELL,
	))

/datum/component/usb_port/proc/unregister_physical_signals()
	if (isnull(physical_object))
		return

	UnregisterSignal(physical_object, list(
		COMSIG_MOVABLE_MOVED,
		COMSIG_PARENT_EXAMINE,
	))

/datum/component/usb_port/proc/attach_circuit_components(obj/item/integrated_circuit/circuitboard)
	for(var/obj/item/circuit_component/component as anything in circuit_components)
		circuitboard.add_component(component)
		RegisterSignal(component, COMSIG_CIRCUIT_COMPONENT_REMOVED, .proc/on_circuit_component_removed)

/datum/component/usb_port/proc/on_examine(datum/source, mob/user, list/examine_text)
	SIGNAL_HANDLER

	if (isnull(attached_circuit))
		examine_text += span_notice("There is a USB port on the front.")
	else
		examine_text += span_notice("[attached_circuit.shell || attached_circuit] is connected to [parent.p_them()] by a USB port.")

/datum/component/usb_port/proc/on_examine_shell(datum/source, mob/user, list/examine_text)
	SIGNAL_HANDLER

	examine_text += span_notice("[source.p_they(TRUE)] [source.p_are()] attached to [parent] with a USB cable.")

/datum/component/usb_port/proc/on_atom_usb_cable_try_attach(datum/source, obj/item/usb_cable/connecting_cable, mob/user)
	SIGNAL_HANDLER

	if (!length(circuit_components))
		set_circuit_components(circuit_component_types)

	var/atom/atom_parent = parent

	if (!isnull(attached_circuit))
		if(user)
			atom_parent.balloon_alert(user, "usb already connected")
		return COMSIG_CANCEL_USB_CABLE_ATTACK

	if (isnull(connecting_cable.attached_circuit))
		if(user)
			connecting_cable.balloon_alert(user, "connect to a shell first")
		return COMSIG_CANCEL_USB_CABLE_ATTACK

	if (!IN_GIVEN_RANGE(connecting_cable.attached_circuit, parent, USB_CABLE_MAX_RANGE))
		if(user)
			connecting_cable.balloon_alert(user, "too far away")
		return COMSIG_CANCEL_USB_CABLE_ATTACK

	if (connecting_cable.attached_circuit.locked)
		connecting_cable.balloon_alert(user, "shell is locked!")
		return COMSIG_CANCEL_USB_CABLE_ATTACK

	usb_cable_ref = WEAKREF(connecting_cable)
	attached_circuit = connecting_cable.attached_circuit

	connecting_cable.forceMove(attached_circuit)
	attach_circuit_components(attached_circuit)
	if(user)
		attached_circuit.interact(user)

	var/new_physical_object = attached_circuit.shell
	if(!new_physical_object)
		new_physical_object = attached_circuit

	RegisterSignal(attached_circuit, COMSIG_CIRCUIT_SHELL_REMOVED, .proc/on_circuit_shell_removed)
	RegisterSignal(attached_circuit, COMSIG_PARENT_QDELETING, .proc/on_circuit_deleting)
	RegisterSignal(attached_circuit, COMSIG_CIRCUIT_SET_SHELL, .proc/on_set_shell)
	set_physical_object(new_physical_object)

	return COMSIG_USB_CABLE_ATTACHED

/datum/component/usb_port/proc/set_physical_object(atom/movable/new_physical_object)
	if(physical_object)
		unregister_physical_signals()
	if(usb_cable_beam)
		QDEL_NULL(usb_cable_beam)

	var/atom/atom_parent = parent
{'fraction_non_alphanumeric': 0.0847697756788666, 'fraction_numerical': 0.0007083825265643448, 'mean_word_length': 6.785539215686274, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1060635', 'n_tokens_mistral': 4786, 'n_tokens_neox': 4378, 'n_words': 673}