Last active
January 5, 2017 00:26
-
-
Save SeijiEmery/d258e14f0faa381fd12e to your computer and use it in GitHub Desktop.
C++ Dependency Injection
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
#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 |
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
#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 |
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
// 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