A sort of 'content management system' for Cinder applications
Contains various classes to manage data and facilitate reactive programming
- cms::Model -- Container for key-values pairs that lets you register callbacks when its content changes
- cms::ModelCollection -- Container for list (collections) of models that lets you register callbacks for mutations (additions/removals) from the collection
The cms::cfg namespace contains various classes that aim to make "configurable" application logic.
Originally this method was developed specifically for UI-specific purposes with the goal to separate configuration-logic from application-logic. UI involves a lot of data; every scene item/component has to be configured, positioned, styled, etc. and all this configuration obfuscates the logical structure of the application.
What started as an effort to "configure" classes based on json data (inspired by how CSS stylesheets style an HTML document) evolved into a system that lets you instantiate entire (pieces of) applications from a configuration file. This approach is very much inspired by VPLs like MaxMSP and Touch Designer that have highly modular and independent building blocks that can be wired together to perform complex tasks.
This implementation is made as part of exploratory research into different ways of creating and organising logic and data. Though parts of it have actually been used in practice, in its current (immature) form it introduces more problems (for most cases) than it solves. Specifically it fails to provide clear insight in the interactions and dependencies between the components that make up the application and creating new components that are ready to be used in this systems proofs fairly cumbersome and counter-intuitive.
What do you need?
- A configuration file
- Some logic that defines how classes and their properties can be confiugured
Below is an example configuration file (from the texsphere example in this repository) that defines 4 components;
- A Runner that emits a
draw
events and connects its drawState to the globaldrawEnabled
- A Camera that listens for the
draw
event, then sets its matrices and emits thecam1
event - A TexSphere that draws itself when the
cam1
event is emitted - A Keyboard component for user input that toggle the
drawEnabled
state when thed
key is pressed
{
"Cfgr": {"active": true},
"Runner": {"drawEmit": "draw", "drawState":"drawEnabled"},
"Runner.Camera": {"on": "draw", "emit":"cam1", "pos": "0,0,0", "lookAt": "0,0,0.5", "ui":true},
"Runner.TexSphere": {"radius": 10, "pos": "0,0,0", "tex":"equi.jpg", "drawOn":"cam1", "texOffset":"0.8,0"},
"Runner.Keyboard": {"key:d": "toggle:drawEnabled"}
}
Below is the example's setup function that shows how different components are registered and then the entire application structure is loaded dynamically at runtime from the config file.
void MainApp::setup(){
// Register supported components
builder.addCfgObjectInstantiator<component::Runner>("Runner");
builder.addCfgObjectInstantiator<component::Keyboard>("Keyboard");
builder.addCfgObjectInstantiator<component::TexSphere>("TexSphere");
builder.addCfgObjectInstantiator<component::Camera>("Camera");
// Load configuration file
builder.getModelCollection().loadJsonFromFile(ci::app::getAssetPath("config.json"));
// build (part of) our application hierarchy by instantiating the 'Runner' component
// and all of its child componnents
pRunner = builder.build<component::Runner>("Runner");
}
Finally here is the configuration logic of TexSphere (one of the component classes);
void TexSphere::cfg(cms::cfg::Cfg& cfg){
// configurables
std::string texAssetPath;
glm::vec3 pos;
glm::vec2 texOffset;
float radius = 1.0f;
cfg
.connectAttr<void()>("drawOn", [this](){ this->draw(); })
.set("tex", texAssetPath)
.setVec3("pos", pos)
.setFloat("radius", radius)
.setVec2("texOffset", texOffset);
this->setup(texAssetPath, pos, radius, texOffset);
}