Last active
August 29, 2015 14:06
-
-
Save SeijiEmery/dff1cc2d28e13cfcd44b to your computer and use it in GitHub Desktop.
Snippet for a fast logic gate VM.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
enum class NodeType { | |
InactiveNode, | |
AndGate, | |
OrGate, | |
XorGate, | |
NotGate, | |
NandGate, | |
NorGate, | |
Input, // Used for component I/O, and to implement more complicated logic components (buttons, displays, etc) | |
Output | |
}; | |
struct LogicNode { // Should fit into one 64-bit register (8 bytes) | |
// Node type (should be 8-bits) | |
NodeType type = NodeType::InactiveNode; | |
// Reserved for future use | |
uint8 flags = 0x0; | |
// Indices/references to current (owned state), left child/input, and right child/input (not used by all nodes) | |
uint16 iref = 0, lref = 0, rref = 0; | |
}; | |
class LogicSim { | |
protected: | |
// Logic gate description and current routing state (for basic logic components) | |
std::vector<LogicNode> nodes; | |
// // Contains data for more complex nodes/logic components | |
// std::vector<LogicComponent> specialNodes; | |
// Logic gate state (duplicated 2x like a frame buffer – you have the current, 'active' state that's being used, | |
// and then a next, inactive state that can be written to and worked on without altering the read-from state) | |
std::vector<bool> nodeState0; // The stl implements these as bitmask arrays (memory overhead is very low, so | |
std::vector<bool> nodeState1; // we could probably fit even large simulation states into a single 4kb cache line) | |
bool curState; | |
std::vector<bool> & getCurrentState () { | |
return curState ? nodeState0 : nodeState1; | |
} | |
// Used for internal memory management (keeping track of freed nodes/state entries) | |
std::vector<uint> freeNodeStack, freeStateStack; | |
}; | |
void LogicSim::simulateStep () { | |
curState = curState ? false : true; | |
auto & currentState = curState ? nodeState0 : nodeState1; | |
auto & nextState = curState ? nodeState1 : nodeState0; | |
// Optimization note: | |
// The data is small enough that it can fit entirely within the L1 cache – thus this code (with its heavy | |
// memory i/o) will run extremely fast. Size isn't really a problem since we max out at 65k nodes (uint16 | |
// limitation), and the node descriptor (which can be streamed from the L2) is only 400kb for 50k nodes, while | |
// the node *state* – basically two bit arrays – fits in 13k (for 50k nodes) and can just sit on the L1 | |
// for the duration of the sim update. | |
// Simulate basic, built-in nodes (very fast) | |
for (auto node : nodes) { | |
switch (node.type) { | |
case LogicNode::InactiveNode: break; | |
case LogicNode::AndGate: nextState[node.iref] = currentState[node.lref] & currentState[node.rref]; break; | |
case LogicNode::OrGate: nextState[node.iref] = currentState[node.lref] | currentState[node.rref]; break; | |
case LogicNode::XorGate: nextState[node.iref] = currentState[node.lref] ^ currentState[node.rref]; break; | |
case LogicNode::NotGate: nextState[node.iref] = !(currentState[node.lref]); break; | |
case LogicNode::NandGate: nextState[node.iref] = !(currentState[node.lref] & currentState[node.rref]); break; | |
case LogicNode::NorGate: nextState[node.iref] = !(currentState[node.lref] | currentState[node.rref]); break; | |
case LogicNode::XnorGate: nextState[node.iref] = !(currentState[node.lref] ^ currentState[node.rref]); break; | |
case LogicNode::BufferGate: nextState[node.iref] = currentState[node.lref]; break; | |
case LogicNode::Output: nextState[node.iref] = currentState[node.lref]; break; | |
case LogicNode::Input: break; | |
} | |
} | |
// // Simulate other, custom nodes (components and stuff using Input/Output nodes, etc) that | |
// // are too complicated to be represented/simulated by the above. | |
// for (auto node : specialNodes) { | |
// node.simulate(currentState, nextState); | |
// } | |
} | |
LogicNode & LogicSim::createNode(NodeType type, bool startingState = false) { | |
auto & node = freeNodeStack.size() != 0 ? nodes[freeNodeStack.pop_back()] | |
: (nodes.push_back(), nodes.back()); | |
node.type = type; | |
node.iref = freeStateStack.size() != 0 ? freeStateStack.pop_back() | |
: (nodeState0.push_back(), nodeState1.push_back(), nodeState0.size() - 1); | |
nodeState0[node.iref] = nodeState1[node.iref] = startingState; | |
return node; | |
} | |
LogicNode & LogicSim::getNode (uint idx) { | |
return idx < nodes.size() ? nodes[idx] : nodes[0]; | |
} | |
bool LogicSim::getNodeState (uint idx) const { | |
return idx < nodes.size() ? getCurrentState()[nodes[idx].iref] : false; | |
} | |
void LogicSim::deleteNode (uint idx) { | |
if (idx >= nodes.size()) | |
return; | |
nodes[idx].type = NodeType::InactiveNode; | |
freeStateStack.push_back(nodes[idx]); | |
freeNodeStack.push_back(idx); | |
nodes[idx].iref = 0; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Design approach:
– sim/vm models the relationship between logic nodes, and possesses the node state(s) which are updated each simulation step. Model code doesn't have anything to do with view code, so the simulation can be optimized on its own.
– view code (and view models) are built on top of that, and don't have anything to do with the actual simulation (it just queries the sim for node state and connections so that it can properly render stuff). Can be optimized for rendering on its own, and multiple views / view models could exist that use the same underlying sim code. (eg. could provide a stylized 'tron mode' w/ tons of sweet rendering fx in addition to the regular editor view)
– the controller wires everything together, notably keeping the 'model' and 'view model' data structures synchronized, and handling creation, modification, and destruction of logic nodes as a whole. The controller is event-driven, and the events are dispatched by a FSM that responds to user input (mouse clicks, drag begin/end, etc).