ID:151999
 
I need a system to simulate electricity. For instance, you have a generator, and wires connected to it. Let's say you connect something that needs electricity to the wire. Now it works.

I can't think of an efficient way to do this (I don't really want to, for instance, have a proc that follows a wire in every direction until it finds a generator (or doesn't)). Ideas?
You'll want to make a datum for the wiring system, and store all the wires and their connections in there. See SS13 for an example of this.

-- Data
I'm not sure if this is what you meant, but I whipped up some code:

obj/electricity
var
electricity

proc/SetElectricity(value)
electricity = value
for(var/search_dir in list(NORTH,SOUTH,EAST,WEST,NORTHEAST,NORTHWEST,SOUTHEAST,SOUTHWEST))
var/obj/electricity/e = locate(/obj/electricity) in get_step(src,search_dir)
if(istype(e,/obj/electricity) && e.electricity != value) e.SetElectricity(value)

// debugging:
var/initial = copytext(name,1,2)
if(electricity) text = "<font color=blue>[initial]</font>"
else text = "<font color=red>[initial]</font>"

wire
New()
. = ..()
SetElectricity(0)

generator
New()
. = ..()
// this is a hacky way to have generators get priority over wires:
spawn(1) SetElectricity(1)


It may be wise to alter the way wires behave, but you get the general idea.
In response to DivineO'peanut
The wires should cache the search_dir of the next connecting wire, and if it's non-existant, search for it. This can be done even better by recording the direction of the next wire. I'd also assign each line of wire a unique ID to ensure connection (in case you lay 2 wires in one location).
The most robust way (not the easiest) to do this is to pulse from the source using a breadth-first search to determine all of the connected components along the wires, then build a single list of all appliances in the network. That way you don't waste CPU cycles transmitting power from source-to-wire-to-wire-to-wire-...-to-destination when the important thing is transmitting from source-to-destination.

In other words, once you know that it's linked to the electricity system, consider the appliance to be linked directly to the generator. This is actually not far from reality, since during electrical transmission the electrons don't flow very far very fast: instead, the electricity produces an electromagnetic wave which runs along the wires.

Thus, build a pool of kilowatts from the generator and have all loads drain from that pool directly. You might also want to count the number of wires and consider that a single load (subtracted before any other appliances get energy) so you can include a waste electricity drain due to wire resistance.

If a node (a wire, or an appliance which can be plugged into) is ever destroyed in the network, you'll have to re-evaluate the linkage of the system. You can make this powerful (but difficult), by tracking which nodes are linked to which and doing a quick list intersection, or simple (but inefficient), by doing the same search you took in the beginning to find the network in the first place and then comparing the list found to the former list -- anything not in the new list shuts down.

Connecting to the network is usually very easy -- just appending to the list. Removing from the network is also quite easy if the appliance can't be used as a node in the network, meaning that the appliance only acts as a consumer and cannot be plugged into.


[edit]If you really want to get fancy, ignore the wires completely and use an EM field for your power source. Problem solved -- all generators put energy into the air and all appliances pluck it out. This is the mechanism that Total Annihilation uses, which is quite elegant in its own way.


[edit2]Just some technical notes on the concept of electricity. In Interstellar Inc., I had originally planned on a system that would build a pool of generated energy and allow both instantaneous drains from that pool (direct energy consumption) as well as power drains from that pool (energy over time). This turned out to be a very bad idea, because generators produced power, not energy, and varied their power output to meet demand. When the power drain is instantaneous, the power required is technically infinite, even if the energy is a fixed constant. This complicated things way more than they had to be, so I found a simple solution: "instantaneous" appliances have built-in capacitors to charge up, and all appliances drain from the network over time. Whenever you use an instantaneous appliance, it consumes its stored energy, then consumes power to recharge.
In response to CaptFalcon33035
The problem with caching here is that wires could always be moved or created, so even if you knew the direction of the surrounding wires you'd have to search for new wires. However, I can see the benefit of this if wires next to each other were not necessarily connected (you'd manually create connections, perhaps via a list).

I'm tempted to update my code to do this, but Jtgibson's spectacular post would put my efforts to shame. :-(
In response to Jtgibson
Mathematically speaking, this is a basic graph problem. Each generator, appliance, or junction box is a node in the graph, and each fully connected wire is an edge. If you consider each one to be in either a powered or unpowered state, calculating the rest is easy.

Any time an edge is added, if it touches a powered node then it is now a powered edge. Likewise if a node is added to a powered edge, it becomes a powered node. Any time an edge or node powers up, recursively power up any unpowered nodes/edges connected to them.

When a powered edge or node is removed (it doesn't matter for unpowered ones), graph connectivity has to be rechecked.

Going by your energy/power example, checking powered status isn't quite enough. The easiest method is probably to keep a graph datum which has a list of its nodes and edges. When cutting out an edge or node, the graph it belongs to may split into several smaller graphs; this basically just requires a traversal, creating new graph datums for any elements that are no longer connected. Adding a node or edge can join multiple graphs into one. If a node knows which graph it belongs to, it's fairly trivial to search the nodes to find out how many generators are available and the amount of power each can sustain. The generators can do this too, so they can adjust their output based on the relative strength of each and the current load.

graph
var/list/items

proc/Combine(graph/G)
items += G.items
for(var/graphitem/I in G.items) I.graph = src
G.items = null

proc/CheckIntegrity()
if(!items || !items.len) return
var/list/olditems = items
items = new
var/graphitem/I
for(I in olditems) I.graph = null
var/graph/G = src
while(olditems.len)
I = pick(olditems)
if(!G) G = new
I.graph = G
if(!G.items) G.items = new
G.items += I
I.Propagate()
olditems -= G.items
G = null

proc/AddNode(graphitem/node/N, list/edges)
items += N
N.graph = src
if(edges)
N.edges = new
for(var/graphitem/edge/E in edges)
if(E.node1 && E.node2) continue
if(!E.node1) E.node1 = N
else E.node2 = N
N.edges += E
N.Propagate()

proc/AddEdge(graphitem/edge/E, graphitem/node/N1, graphitem/node/N2)
items += E
E.graph = src
if(N1)
E.node1 = N1
if(!N1.edges) N1.edges = new
N1.edges += E
if(N2)
E.node2 = N2
if(!N2.edges) N2.edges = new
N2.edges += E
E.Propagate()

graphitem
var/graph/graph
proc/Disconnect()
if(graph)
if(graph.items) graph.items -= src
graph.CheckIntegrity()
graph = null
proc/Propagate()

node
var/list/edges

Disconnect()
for(var/graphitem/edge/E in edges)
if(E.node1 == src) E.node1 = null
if(E.node2 == src) E.node2 = null
edges = null
..()

Propagate()
for(var/graphitem/edge/E in edges)
if(E.graph != graph)
if(E.graph) E.graph.items -= E
if(graph) graph.items += E
E.graph = graph
E.Propagate()

edge
var/graphitem/node/node1
var/graphitem/node/node2

Disconnect()
if(node1 && node1.edges) node1.edges -= src
if(node2 && node2.edges) node2.edges -= src
node1 = null
node2 = null
..()

Propagate()
if(node1 && node1.graph != graph)
if(node1.graph) node1.graph.items -= node1
if(graph) graph.items += node1
node1.graph = graph
node1.Propagate()
if(node2 && node2.graph != graph)
if(node2.graph) node2.graph.items -= node2
if(graph) graph.items += node2
node2.graph = graph
node2.Propagate()


Note the concept above actually allows for edges that are singly connected.

Lummox JR
In response to Lummox JR
Could such concepts also apply to non-laggy ways to make pieces fall off an airship if they are cut free?
In response to I-Aryn-I
Yes.