Skip to content

Instantly share code, notes, and snippets.

@SeijiEmery
Last active January 5, 2017 00:26
Show Gist options
  • Save SeijiEmery/d258e14f0faa381fd12e to your computer and use it in GitHub Desktop.
Save SeijiEmery/d258e14f0faa381fd12e to your computer and use it in GitHub Desktop.
C++ Dependency Injection
#pragma once
namespace defaults {
// Default impl of an AABB, and vector / matrix math.
// Data structures are implemented as PoDs; operations are defined as statics in a separate class.
struct AABB {
float x1, y1, x2, y2;
};
struct Vec2 {
float x, y;
};
struct Mat3 {
float[9] data;
};
// Defines operations for the above math types.
// This allows you to replace this impl with your own or external types -- just write
// an adapter class w/ the same type signatures as the following, but using your own
// types.
// Used by uiframe.hpp.
//
// There are no interfaces for this -- since we're using C++ templates (and the entire point of
// this approach is to enable dependency injection without adding runtime overhead).
// You can think of this as a duck-typed interface -- the spec is there, but the compiler has a
// roundabout way of enforcing it -- if you do not implement this properly you will get obscure
// compiler errors (likely in the uiframe file), so if you do override it make sure you doublecheck
// all of the type signatures ;)
struct Math2d {
static Vec2 clone (const Vec2 & v);
static Mat3 clone (const Mat3 & m);
static Mat4 clone (const Mat4 & m);
static AABB clone (const AABB & r);
static Vec2 & add (Vec2 & a, const Vec2 & b);
static Vec2 & scale (Vec2 & a, float s);
static float dot (const Vec2 & a, const Vec2 & b);
static Vec2 cross (const Vec2 & a, const Vec2 & b);
static Vec2 & negate (Vec2 & a);
static Vec2 & mul (const Mat3 & m, Vec2 & v);
static Mat3 & mul (Mat3 & a, const Mat3 & b);
static Mat3 & scale (Mat3 & mat, float s);
// etc...
};
}; // namespace defaults
#pragma once
// Simple UI Library built around 'frames' (2d panels that can have events attached to them. View information and
// actual rendering is provided externally).
// Instead of enclosing our library inside of a namespace:
// namespace ui {
// ...
// }; // namespace ui
// , we enclose it inside of a templated struct:
// template <Dependencies...>
// struct UI {
// ...
// }; // struct UI
// typedef UI<Default impls> ui;
//
// This means you can't split the lib across multiple files, but you could use includes for that...
// (not great, but the templating thing is really powerful)
template <class Vec2, class AABB, class Mat3, class Math>
struct UI {
typedef unsigned EVENT_ID;
typedef std::function<void(UIFrame&)> EventHandler;
class IView {};
class IController {};
class IModel {};
struct UIFrame {
AABB bounds;
Mat3 transform;
UIFrame * parent;
uint8_t flags;
IView * view;
IModel * model;
IController * controler;
std::map<EVENT_ID, EventHandler> eventHandlers;
};
static void exampleOfAMathOperation (UIFrame & frame, const Vec2 & velocity, float dt, const Mat3 & worldTransform) {
Vec2 localVel = Math::mul(worldTransform, Math::clone(velocity)); // I've clearly been writing too much java
Math::scale(localVel, dt);
Math::translate(frame.transform, localVel);
// If the math class is just an adapter, these wrapper calls will get optimized away, so if you were using glm
// (for example), this code would be just as fast as using that library directly (but with the added benefit that
// you could swap out that implementation for your own internal math library instead (or the default provided
// version), and this code would use the optimized version of that instead.
}
template <class T>
class SharedResource {
public:
static std::shared_ptr<T> getResource (const std::string & name) {
// ...
}
};
struct SharedButtonResource : public SharedResource<SharedButtonResource> {
std::shared_ptr<TexturedMesh> defaultTexture;
std::shared_ptr<TexturedMesh> focusedTexture;
std::shared_ptr<TexturedMesh> clickedTexture;
SharedButtonResources (const std::string & resourceName) {
defaultTexture = resources::getTexture(resourceName, "default");
focusedTexture = resources::getTexture(resourceName, "focused");
clickedTexture = resources::getTexture(resourceName, "clicked");
}
};
struct ButtonData : public IModel {
// TexturedMesh * texture;
std::shared_ptr<SharedButtonResource> resources;
std::unique_ptr<TextLabel> text;
bool hasFocus;
bool isDown;
};
class ButtonView : public IView {
void render (const UIFrame & frame, Batch & batch) {
ButtonData * button = dynamic_cast<ButtonData*>(frame.model);
if (button.isDown)
batch.draw(button->resources->clickedTexture, frame.transform);
else if (button.hasFocus)
batch.draw(button->resources->focusedTexture, frame.transform);
else
batch.draw(button->resources->defaultTexture, frame.transform);
batch.draw(button->text, frame.transform);
}
};
static auto makeButton (const std::string & text, EventHandler onClick) -> decltype(makeUIFrame()) {
auto frame = makeUIFrame();
auto text = makeTextLabel();
// TexturedMesh * defaultTexture = resources::getTexture("button", "default");
// TexturedMesh * focusedTexture = resources::getTexture("button", "focused");
// TexturedMesh * clickedTexture = resources::getTexture("button", "clicked");
auto model = makeModelData<ButtonData>();
frame.model = model;
model->text = std::move(text);
model->resources = ResourceManager::getSharedResource<SharedButtonResource>("button");
onEvent(frame, EVT_MOUSE_ENTER, [](UIFrame & frame) {
ButtonData * button = dynamic_cast<ButtonData*>(frame.model);
button->hasFocus = true;
});
onEvent(frame, EVT_MOUSE_EXIT, [](UIFrame & frame) {
ButtonData * button = dynamic_cast<ButtonData*>(frame.model);
button->hasFocus = false;
});
onEvent(frame, EVT_BTN_DOWN, [](UIFrame & frame) {
ButtonData * button = dynamic_cast<ButtonData*>(frame.model);
button->isDown = true;
});
onEvent(frame, EVT_BTN_UP, [](UIFrame & frame) {
ButtonData * button = dynamic_cast<ButtonData*>(frame.model);
button->isDown = false;
if (button->hasFocus)
onClick(frame);
});
return frame;
}
}; // struct UI
#ifndef USE_CUSTOM_MATH_LIB
typedef UI<defaults::Vec2, defaults::AABB, defaults::Mat3, defaults::Math2d> ui;
#else
typedef UI<MATH_DEP_Vec2, MATH_DEP_AABB, MATH_DEP_Mat3, MATH_DEP_MathAdapter> ui;
#undef USE_CUSTOM_MATH_LIB
#undef MATH_DEP_Vec2
#undef MATH_DEP_AABB
#undef MATH_DEP_Mat3
#undef MATH_DEP_MathAdapter
#endif
// MyRect.hpp
class MyRect {
// ...
}
// MyMathAdapter.hpp
#include <glm/glm.hpp>
#include "MyRect.hpp"
template <class Vec2 = glm::vec2, class Mat3 = glm::mat3, class Rect = MyRect>
class MyMathAdapter {
static Vec2 & add (Vec2 & a, const Vec2 & b) { return a += b; }
//...
static Vec2 clone (const Vec2 & v) { return Vec2 { v }; }
static Mat3 clone (const Mat3 & m) { return Mat3 { m }; }
static Rect clone (const Rect & r) { return Rect { r }; }
//...
}
// ui.hpp
#include <glm/glm.hpp>
#include "MyRect.hpp"
#include "MyMathAdapter.hpp"
#include "uilib/uiframe.hpp"
typedef UI<glm::vec2, MyRect, glm::mat3, MyMathAdapter> myui;
// or just:
#include <glm/glm.hpp>
#include "MyRect.hpp"
#include "MyMathAdapter.hpp"
#define USE_CUSTOM_MATH_LIB
#define MATH_DEP_Vec2 glm::vec2
#define MATH_DEP_Mat3 glm::mat3
#define MATH_DEP_AABB MyRect
#define MATH_DEP_MathAdapter MyMathAdapter
#include "uiframe.hpp"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment