tags: Guide, Technical, Backend
# Signals, Components, and Elements
DCS is complicated. Not just because it uses clever solutions for complicated problems, the way you have to think about those problems changes drastically as well. Don't panic though, it's a bit of a pain to start but after the initial learning period it just clicks. It's a whole new way of approaching problems that simplifies many usecases common in ss13.
**DCS, Datum component system**: Bit of an outdated term now but this was the original name of the system as a whole
**Components**: Isolated holders of functionality. They hold all the data and logic necessary to accomplish some discrete behaviour.
**Elements**: The same thing as components but cheaper and more limited in functionality. Components will often be used in this guide to refer to both as a group.
**Signals**: The way components receive messages. Originally only components could receive signals but since then it has been expanded so anything can receive a signal if it's useful.
Components are blackbox bundles of functionality designed from the ground up with signals in mind. The specifics of how signals work will be expanded on below but you'll likely first interact with signals by making changes to a component or element somewhere. Elements are functionally a minimal version of components and for the purpose of this document you can replace most uses of the word component with element. The differences are expanded on later if you want to get into the details.
To name a few existing example components, we have a component which handles an object making noise when hit/thrown/used/etc. You can apply this to anything, even walls if you want. A component which turns anything into a storage item. A component which handles rotating things by hand in character. A component that makes diseases spread. You get the point.
Signals are our implementation of a fairly common programming concept: events. If you have code that's supposed to trigger when some conditions are met you could write your code to check for those conditions every tick, this is a polling pattern. Events in contrast are triggered by the conditions themselves.
To give an example, imagine how footsteps make noise. In the shoe code you could do the following:
. = ..()
var/turf/current_turf = get_turf(src)
if(current_turf == last_noise)
last_noise = current_turf
Here, every process tick, we're seeing if we're in a new location and if so we make a footstep sound. Wasteful to say the least.
Instead, shoes could be set up to only make sound when the person wearing them moves. Since shoes are only worn by humans, without signals you would need to put this code in the human type. This is also a sub par solution as it means you have logic that belongs to shoes outside of shoe code.
With signals though, all these problems are solved. Human code doesn't need to know or care that something it's wearing is listening to when it moves, and shoe code doesn't need to check every tick if it's moved somewhere new.
RegisterSignal(equipper, COMSIG_MOVABLE_MOVED, .proc/play_footstep)
## Signals: How they work
They're fairly simple in concept: First you create a place the signal gets activated from. Let's have a signal that activates when someone walks over it.
#define COMSIG_MOVABLE_CROSSED "movable_crossed"
SEND_SIGNAL(src, COMSIG_MOVABLE_CROSSED, AM)
Here we have the base Crossed proc set up to send a signal and do nothing else. `src` is given as an argument so that it is a signal with itself as the origin. Next up the signal type is given, it is defined in __DEFINES/components.dm then passed in to the signal. Functionally it's just a string acting as an identifier. The third argument in the signal is the movable that's crossing over, this gets passed to whoever is receiving the signal.
Now that we have a signal at the top of the inheritance chain, any other overrides of the Crossed proc will have to either call parent with `..()` or send the same signal like we did here in order to make sure that signal is called whenever the proc is called. **Avoid making duplicate signals as much as possible.** Signals should have a single source of origin so it is as easy as possible to reason about behavior when receiving those signals. If you feel the need to make additional signal sources you should consider instead just making additional types of signals. It's free to make more types of signals.
Now that this new signal exists we can make something able to listen for the signal.
RegisterSignal(parent, COMSIG_MOVABLE_CROSSED, .proc/play_squeak_crossed)
Here, when the object, a component in this case, is created, it registers for the signal. It is now "listening" for that signal and any time that signal is sent the component will know about it. Any arguments given in that signal get passed along to the listener. This then calls the proc specified when registering for the signal. You can make any proc get called this way, check out callbacks to see how that works.
Where did that datum/source come from? Signals will in addition to their normal arguments pass the originator of the signal as the first argument every time. This is useful when you have something listening for the same signal on multiple other objects.
You now have a working component receiving a signal from the object it's applied to, but what's a component?
## Sealing away the bad code with components
Components are the original reason signals were added. Signals may have moved on to bigger things but components are still a very powerful tool for having complex functionality that is easy to maintain. Components are only in charge of themselves, they have, in general, simple responses to simple events. They don't care what else their owner is doing or how it's doing it. If the squeak component receives a signal saying that someone has walked on its owner then by god it's going to play a sound.
As previously mentioned the primary way components interact with the world is through signals. There are a couple other ways though that I'll go over fast.
The component has a reference to the owner of that component. This is most commonly used to get some state information like the location of the owner to know where to play a sound etc.
The other way you can interact with components is through the `GetComponent()` proc. This proc is, plain and simple, a crutch. What it does is allow you to get a given type of component that's been applied to an object. This method of interacting with a component hurts the entire usefulness of them. If you have a component that depends on external code to do functionality, you're moving back once more to spaghetti code. You cant know if you see all the possible paths logic can take just by looking at the component. Look for other methods first and ask for help if you still feel that you really need to use `GetComponent()`
This bears repetition: **The GetComponent proc is a crutch and hurts your component. Avoid it if at all possible.**
Take a look at the following code. It is a component which poorly implements a way to modify the value of a sold item
var/value = 0
var/datum/component/value/comp = GetComponent(/datum/component/value)
value = comp.value
Note the component isn't actually doing anything. It is not compartmentalizing anything. We're just using a component to hold a value for the sake of saying we're using a component.
There are many ways to do this properly, all of them involve signals:
var/value = 0
RegisterSignal(parent, COMSIG_ITEM_APPRAISING, .proc/appraising)
source.value = value
We've removed the GetComponent, no longer is this object depending on that component existing. Everything in the item is generic and the component handles exceptional cases. Other components could also make use of this same signal if their effects are related to the value of an item.
Keep the code for a particular feature as isolated as possible in their respective components and you'll end up with a more maintainable, more extendable, and more bug resistant system. The costs in performance are minimal for all this and in many cases cheaper on the cpu. Memory wise there are potentially issues in extreme examples but this is what elements are for.
## Optimizing with elements
The short of it is elements are lightweight components. Only one instance per type of element exists and that single instance attaches itself to every object that makes use of that element. This is to allow for compartmentalizing behavior that isn't particularly viable in components due to memory constraints or for shared global state behavior.
## Designing new components/elements
If you want to make a new component, stop and consider what it is exactly you're trying to accomplish. Components should generally be created as holders for behavior in isolation of how that behavior gets used. A wizard component including requirement for robes and such is probably not as good a component as a generic spellcaster component which gives something the ability to cast spells.
After deciding on the concept for your component you may want to consider an element instead if that component will be used heavily or is very simple. Components have more flexibility than elements but elements are very cheap to use. Don't worry too much if you can't decide and just make a component if you can't see how an element would work.
You may have found a component or element that does almost what you want so you want to work off of that, great! If it's a component **don't make a subtype**. Components have everything they need to have all behavior for all usecases in a single type. If you need another component you should probably split that component into multiple different components. If it's an element on the other hand this isn't quite true. You shouldn't be making subtypes of elements to add new behavior, that can easily all go in to one element if your scope is properly narrow. If you'd like some minor configuration in your element you want a bespoke element. (May expand guide to talk about these later)
If this is your first time making a new component you should now get some feedback from others regarding your planned design. Getting into the right mindset is hard for your first few components and others who are more experienced can help you with issues before they happen.