Last active
January 5, 2017 00:29
-
-
Save SeijiEmery/cc143e415778aa0cfc57 to your computer and use it in GitHub Desktop.
C++ example snippets for a logic sim project
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
// Created by Seiji Emery on 9/16/14. | |
// Copyright (c) 2014 Seiji Emery. All rights reserved. | |
// | |
// Base class for logic components (logic gates and I/O stuff like buttons and | |
// displays). Defines interface that all of these must implement (boolean internal | |
// state, multiple (0+) inputs, and one output value (retrieved via getState()). | |
// Implementations are stored as a list within the LogicSim class (hence the need | |
// for a uniform interface). | |
class LogicComponent { | |
public: | |
bool getState () const { return currentState; } | |
// First stage of simulation update | |
virtual void updateState () = 0; | |
// Called once every object has been updated | |
void swapState () { swap(currentState, nextState); } | |
protected: | |
bool currentState; | |
bool nextState; | |
}; | |
// Model class | |
class AndGate : public LogicComponent { | |
public: | |
void updateState () override { | |
nextState = input1->getState() && input2->getState(); | |
} | |
protected: | |
weak_ptr<LogicComponent> input1, input2; | |
}; | |
class LogicComponentWidget : public DraggableWidget { | |
// Contains stuff shared between the various logic components (and gate, or gate, | |
// buttons, etc). Does not include wires. | |
}; | |
// View class | |
class AndGateWidget : public LogicComponentWidget { | |
public: | |
void render (RenderContext &ctx) { | |
// Do rendering stuff... | |
} | |
// Inefficient, but whatever. | |
// Not sure how to really handle this... Wire target points should be stored | |
// within the class, but the wire connection logic should probably be handled | |
// outside (click + drag -> connect state inputs/outputs, and create/destroy | |
// wires w/ reference to state | |
vector<const WireDragTarget &> getInputTargets () const { | |
return { input1Target, input2Target }; | |
} | |
vector<const WireDragTarget &> getOutputTargets () const { | |
return { outputTarget }; | |
} | |
protected: | |
// Rendering stuff (textures, styling, etc) | |
... | |
// State reference | |
weak_ptr<AndGate> gate; | |
// Wire drag targets | |
WireDragTarget input1Target; | |
WireDragTarget input2Target; | |
WireDragTarget outputTarget; | |
}; | |
// View class for logic wires (connects widgets) | |
class LogicWireView : public DraggableWidget { | |
public: | |
void beginDrag (vec2 dragPoint) { | |
// Disconnect from begin/end point and associated widget, and follow cursor | |
// until endDrag event is recieved. Most of this is highly implementation | |
// specific, so writing code beyond a general example is kind of useless. | |
// ... | |
// Note: should only respond to drag events at endpoints (function signature | |
// should be changed to reflect this). | |
} | |
void endDrag (vec2 endPoint) { | |
// Determine widget drag point that was dragged on to, and connect there. | |
// If dragged onto nothing, just self destruct (and detach from currently | |
// connected widget). | |
// Functionality should maybe be moved to a controller class; everything | |
// starts to get really fuzzy due to number of object relationships and | |
// number of references | |
} | |
void render (RenderContext &ctx) { | |
if (inputState != nullptr && inputState->getState()) { | |
// Render highlighted 'active' wire | |
} else { | |
// Render inactive wire | |
} | |
} | |
protected: | |
// Reference to the 'input' logic component state. (doesn't care about output) | |
// Note that we're keeping this general, and not using any specific logic state | |
// classes. | |
weak_ptr<LogicComponent> inputState; | |
// Keep references to the drag targets, and the widgets we're attached to | |
// Note: not efficient to keep multiple references; this is just a simple | |
// example | |
weak_ptr<WireDragTarget> inputTarget, outputTarget; | |
weak_ptr<LogicComponentWidget> inputWidget, outputWidget; | |
}; | |
// Note on how this is all supposed to work: | |
// When a drag event happens on an unused widget drag target, a logic wire is created, | |
// and follows the cursor for the duration of the drag event. | |
// When it is released, it is either connected to a target widget (if overlapping widget | |
// drag target), or is destroyed (if was released outside of one). | |
// When a wire is dragged from an existing widget, it is detached from its current widget | |
// and follows the rules above. | |
// | |
// When a connection event occurs (wire dragged from one widget onto another), the wire: | |
// - stores references to the drag targets / widgets + target offsets (so that the wire | |
// moves when the widgets move) | |
// - gets a reference to the associated *input* state, and stores that | |
// Additionally, it must: | |
// - connect the output state (retrieved via dst widget reference) to the relevant input | |
// state (via src widget reference) | |
// | |
// When a disconnection event occurs (connection broken), it must | |
// - clear relevant references to drag targets / widgets | |
// - disconnect the relevant input reference from the dst state from the src state | |
// (do this before removing references) | |
// | |
// This could either be done in the LogicWire class (could get messy), or maybe via | |
// a special controller class dedicated to connecting / maintaining the connections | |
// between logic components / wires. This is probably better as it means that the | |
// LogicWire class need to only be concerned with rendering itself, and since the | |
// connection/disconnection operations span multiple views and models. | |
// | |
// Note that: | |
// - The individual widgets don't know anything about the connection (only concerned | |
// with rendering themselves, and maybe updating the wires when they are moved). | |
// - The wires need to know their incoming state (for rendering), and need to keep | |
// references to the widgets so the endpoint position can be updated when they | |
// move. Alternatively, the widget could store the connection to the wires and | |
// handle wire repositioning themselves. In this case, the widgets still don't | |
// know anything about the connection - only the wires and which drag target | |
// they're connected to. | |
// - The logic state (model) doesn't know anything about any of the views, and | |
// doesn't know any of the details about the logic components that it's connected | |
// to, except for their output boolean state. | |
// | |
// Model class | |
class ButtonState : public LogicComponent { | |
public: | |
void updateState () override { | |
// do nothing | |
} | |
void toggleState () { | |
currentState = nextState = currentState ? false : true; | |
} | |
void setState (bool state) { | |
currentState = nextState = state; | |
} | |
}; | |
// View class | |
class ButtonWidget : public LogicComponentWidget { | |
public: | |
void render (RenderContext &ctx) { | |
// do rendering stuff... | |
if (buttonState->getState()) { | |
ctx.draw(buttonDownTexture, ...); | |
} else { | |
ctx.draw(buttonUpTexture, ...); | |
} | |
} | |
void onClick () { | |
buttonState->toggleState(); | |
} | |
protected: | |
// Rendering stuff | |
Texture buttonDownTexture, buttonUpTexture; | |
// ButtonState reference | |
weak_ptr<ButtonState> buttonState; | |
}; | |
shared_ptr<ButtonWidget> createButton (LogicSim & sim, vec2 position, bool initialState) { | |
// Note: I didn't define the constructors (but the meaning should be quite obvious), | |
// and the exact pointer semantics are a bit fuzzy (converting from shared_ptr<ButtonState> | |
// to shared_ptr<LogicComponent>). | |
// | |
// Also, on a more general note, this could make proper destruction rather difficult | |
// since we have two interdependent objects but neither one 'owns' the other, so what | |
// we do during cleanup after deleting a logic component (from the widget) is a tad | |
// bit unclear – esp since we technically have to remove it from a vector owned by | |
// something else... | |
auto buttonState = make_shared<ButtonState>(initialState); | |
sim.addComponent(buttonState); | |
return make_shared<ButtonWidget>(buttonState, position); | |
} | |
// Controller class (encapsulates state models) | |
class LogicSim { | |
public: | |
void simulateStep () { | |
for (auto component : components) { | |
component->updateState(); | |
} | |
for (auto component : components) { | |
component->swapState(); | |
} | |
} | |
protected: | |
std::vector<std::shared_ptr<LogicComponent>> components; | |
}; | |
// View class (encapsulates widget views) | |
class LogicSimView { | |
public: | |
void render (RenderContext &ctx) { | |
for (auto widget : widgets) { | |
widget->render(ctx); | |
} | |
for (auto wire : wires) { | |
wire->render(ctx); | |
} | |
} | |
// Drag events should be handled either via this view class or via a separate | |
// controller. Depends on the implementation. | |
protected: | |
vector<shared_ptr<LogicComponentWidget>> widgets; | |
vector<shared_ptr<LogicWireView>> wires; | |
}; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment