Skip to content

Instantly share code, notes, and snippets.

@SeijiEmery
Last active January 5, 2017 00:29
Show Gist options
  • Save SeijiEmery/cc143e415778aa0cfc57 to your computer and use it in GitHub Desktop.
Save SeijiEmery/cc143e415778aa0cfc57 to your computer and use it in GitHub Desktop.
C++ example snippets for a logic sim project
// 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