Introductory Description
An innovative JSON + C++ development platform that solves several common game development related issues, speeding up the development process, leveraging code reuse potential and being a lot easier to integrate with external development tools than the traditional development approaches. Below in this page the following features are described:
- C++ Universal Container as an Unified Data Structure
- JSON Integration with Support To C++ Types
- JC++ Object “Referencing”, “Copying” and “Lazy-Copying”
- JC++ Object Inheritance and Polymorphism
- JC++ Scripting
- Event System
- Hierarchical State Machine System
- Versioning system
- Timer and Scheduler Systems
- Task Loader
Note: The content of this page and the entire JC++ Platform source code are copyrighted to Samuel Polacchini and were timestamped by www.digistamp.com
Skills
- C++ programming
- Game Engine Architecture
- R&D of solutions for common game dev challenges
Environment
- Visual Studio
- Marmalade C++ (for testing across several compilers and generating Android builds)
- Perforce
- XCode (eventually, for generating iOS builds)
- emscripten (eventually, for generating web builds)
Role
Engine Developer at my own studio
Development Period
Prototyping phase from September, 2014 to November, 2014 and development phase of more than 80% of the currently available features between November, 2014 and July, 2015.
Project Status
The JC++ development platform is currently the base for almost all my C++ coding work, being complete and mature for the problems it was proposed to solve and for the services provided. Eventually, new feature possibilities will be identified and implemented.
Motivation
- Easy game designing and tool integration: Externalizing game settings for designing and tool integration takes considerable coding time and is prone to bugs. Sometimes, the work overhead of supporting a new field for the design results on interesting design ideas not even being prototyped. Engines with integrated tools, such as Unity, offer a solution, but exclude external tools from it and keep configuration data stuck to a closed proprietary software, constantly subject to changes that break them.
- Entity Component System: An architecture supporting it makes all the difference when developing nice and reusable code for games. While we have enough reasons to use C++ for game development, the usual object oriented C++ programming is not friendly to an Entity Component System.
- Memory Management: Dealing with memory on C++ takes coding time and poor approaches results on poor performance and/or on unstable games. It would be great to have a development platform that integrates an efficient and automatic memory management solution for most of the cases while still allowing fine-grained approaches for specific scenarios, which garbage collection based languages don't allow.
- Event-driven programming: Game development is all about handling events and event-driven programming approaches are key for creating decoupled and reusable game components. While languages usually offer solutions for event-driven approaches, it would be interesting to go beyond and have an entire “event-driven architecture”.
Main Programming Tasks Performed
- C++ Universal Container as an Unified Data Structure: The core of the JC++ platform is a powerful universal container in terms of flexibility, features and performance. Using the JC++ platform we rarely need any other kind of container (such the STL containers), almost all our heap allocated data is wrapped by “JC++ objects”, which are hierarchically organized in a unified tree, sharing a common global root. An special kind of JC++ object may behave like an AVL tree or like a double linked list (ordered or not) of children JC++ objects, depending on what we find best for that particular children objects.
Using an one line declaration, new C++ types can be supported, being wrapped by “JC++ objects”, usually by value, which are allocated and released through very fast JC++ object pools, statically compiled for each supported C++ type. Also, type metadata and modified behaviors within the container are optionally supported through overriding the type handler class.
Sample Code: (drag the code with your fingers on mobile)
//A JCObject just wraps a reference to the real data coming from a memory pool specific for the int type
//S(intObjectKey) leads to an unique hash value (unsigned int) at *compilation time*
JCObject intObject( JC( S(intObjectKey), int ) (0) );
intObject = 10; //Assignment to the wrapped int value directly from a int type value, type safe at runtime
int intValue = intObject.AsV<int>(); //get the JCObject content as value (10), type-safe at runtime
JCType& objectType = intObject.GetType(); //type metadata access
DEBUG_LOG(objectType.GetTypeName()); //prints "int" to the console
//Insert the JCObject direclty into the JCObject of the global root. By doing that, this JCObject data will
//persist, without that, it would be released (sent back to the pool) at the end of the current scope.
JC_ROOT.AsObject().Insert(intObject);
JC_ROOT[S(intObjectKey)] = 20; //retrive the JCObject by its key and assign as we made with "intObject"
int newIntValue = JC_ROOT[S(intObjectKey)].AsV<int>(); //newIntValue will get 20
int newIntValueAgain = intObject.AsV<int>(); //also returns 20, we still dealing with the same JCObject data
//Here we just access (by reference) a "Vector2D" JC++ object from an sub-object with key "subObjectKey"
Vector2D& vectorValue = JC_ROOT[S(subObjectKey)][S(vectorObjectKey)].AsR<Vector2D>();
//get the "x" field of the same vector. C++ type fields support is optional, by implementing type metadata
float x = JC_ROOT[S(subObjectKey)][S(vectorObjectKey)][S(x)].AsV<float>();
//this hypothetical sub-object we mentioned above can also be taken as a JCObject (everything can!)
JCObject subObject = JC_ROOT[S(subObjectKey)];
//and by using this JCObject we have a shortcut to access its content, as we do here
subObject[S(floatObjectKey)] = 5.3f;
- JSON Integration with Support To C++ Types: Within the JC++ platform we don’t need to load or parse JSON files, they are automatically mapped into the unified data structure the universal container provides. Additionally, JSON strings, lists or dictionaries can be mapped to JC++ objects of any C++ type for which we specify loading handlers, what means a JSON file within the JC++ platform is a relevant part of the implementation, mapping directly to the runtime data structure we will be dealing with.
Sample Code: (drag the code with your fingers on mobile)
"myJCObject": {
"intObject": 2,
/*"v2" is an alias to the handler C++ function that parses the string into a Vector2D */
"vector2DObject" : "=v2 3.2 8.1"
},
//C++ code
//gets the JSON object "myJCObject" as a JC++ object, being a shortcut to access its fields
//The JSON file will be automatically read and parsed by the statement bellow (only on the first access)
JCObject myJCObject = JC_ROOT[S(DOCS)][S(sample_folder)][S(example__json)][S(myJCObject)];
int intValue = myJCObject[S(intObject)].AsV<int>(); //int field accessed by value
Vector2D vector = myJCObject[S(vector2DObject)].AsV<Vector2D>(); //Vector2D field accessed by value
- JC++ Object “Referencing”, “Copying” and “Lazy-Copying”: A JC++ object can be just a reference to another JC++ object, seamlessly behaving like the referenced object. “Strong” (smart), “weak” and “simple” references are supported. Also it is simple to copy a JC++ object and it is entire sub-tree without dealing with type specificness.
As alternative to normal copying, the “lazy-copy” operation access the copied objects by reference, making the real copy of individual JC++ objects in the sub-tree, only when they are accessed by the first time. Lazy-Copying is very used for cloning complex game objects very quickly, in such way components (sub-objects) and their contents are only really copied if they are they are first accessed and only if they are accessed at all.
From the JSON perspective, you can refer any JC++ object by providing its global or relative path, this includes any JC++ object defined in the same JSON file, independently if it was defined before or after the referencing expression within the file.
Sample Code: (drag the code with your fingers on mobile)
"myObject": {
"intProperty": 2,
/* Copies an external property by using a relative path ('&' refers to the closest parent) */
"floatPropCopy" : "=p float -c &/&/floatProperty" /* copy requires the type to be specified */
},
"floatProperty": 9.1,
/* creates a lazy copy of myObject */
"myObjectLazyCopy" : "=p JCO -l &/myObject", /* JCO stands for the copied type (JC++ object) */
/* creates a strong reference to myObject exemplifing the usage of an absolute path */
"myObjectReference" : "=p -s ROOT/DOCS/sample_folder/example__json/myObject
//C++ code
//gets the JC++ Object for the JSON root, being a shortcut for its children objects
JCObject jsonRootObject = JC_ROOT[S(DOCS)][S(sample_folder)][S(example__json)];
//at this moment, this is an empty object with a reference to the copy source
JCObject myObjectLzCpy = jsonRootObject[S(myObjectLazyCopy)];
//The intProperty is only copied here, at this first access to it
int intValue = myObjectLzCpy[S(intProperty)].AsV<int>();
//Changes the intProperty from a JC++ object that actually is a reference to other one
jsonRootObject[S(myObjectReference)][S(intProperty)] = 7;
//intValue will be 7, this value was changed above through a reference JC++ object
intValue = jsonRootObject[S(myObject)][S(intProperty)].AsV<int>();
//remove myObject from jsonRootObject, however the associated JC++ sub-tree will not be
//released since we have myObjectReference as a *strong* reference to it. Also the
//myObjectLazyCopy establishes a strong reference to it internally.
jsonRootObject.AsObject().Remove(S(myObject));
- JC++ Object Inheritance and Polymorphism: JC++ objects supports (multiple) inheritance and the usage of polymorphic properties and methods through the “including” syntax, which allows a JC++ object to “include” the content of others within 4 possible include modes: “reference”, “optimized reference”, “copy” and “lazy copy”. This is the *base* for reusing and extending JC++ objects data and logic.
Sample Code: (drag the code with your fingers on mobile)
"myGameObject" : {
"intProperty": 3,
"floatProperty" : 5.1,
/* polymorphic simple reference to floatProperty, in "-tr", "t" stands for Target, meaning the path
will be evaluated only at its copied version at the JC++ object that is the access target*/
"polymorphicProperty" : "=p -tr &/floatProperty"
},
"derivedGameObject": {
/* includes as a lazy copy, so the base properties are copied only when first accessed */
"_INCL__myGameObject" : "=p -l &/&/myGameObject",
/* override the floatProperty from the base JC++ object */
"floatProperty" : 32.23
}
//C++ code
JCObject derivedGameObject = JC_ROOT[S(DOCS)][S(sample_folder)][S(example__json)][S(derivedGameObject)];
//tough intProperty is not defined on derivedGameObject, it includes myGameObject, which defines it
int intValue = derivedGameObject[S(intProperty)].AsV<int>();
//thanks to polymorphism, floatValue gets 32.23
float floatValue = derivedGameObject[S(polymorphicProperty)].AsV<float>();
[At this stage I have decided to disclose only the sample codes above, so no sample codes will be provided for the features described bellow]
- JC++ Scripting: JC++ objects can have its own methods implemented through a stack-oriented programming language that fits within JSON lists. This language is not intended for heavy logic implementation, but to put together specific C++ functions that implement the heavy logic stuff very decoupled from each other or from specific kinds of JC++ objects, being highly reusable.
- Event System: Every JC++ object method can also be seen as an event that accepts subscriptions from other methods of any JC++ object. These subscriptions usually happens through weak references, so there is no need for unsubscribing operations before releasing subscribed JC++ objects. This intrinsic fusion between calls and events makes event-oriented logic very natural and easy to apply, leading to a very decoupled logic on JSON and C++ sides. Also, these “event-calls” can be made without the need of previous declaration or definition and can optionally be marked as “registered”, what makes them to be counted and time stamped automatically each time they are invoked. This feature is extensively used, since it is a much easier way of creating counters and time records.
- Hierarchical State Machine System: The concept of “state” is intrinsically supported by the JC++ platform, being its usage optional for a given JC++ object. Being in a “state” is mostly subscribing different methods to specific events (for example a “running” state subscribes a different update method to the “update event” than the “jumping” state). JC++ objects be can set to multiple states at once, but setting an state implies exiting the state currently set for that *same group*. States from one group may have dependencies from states of other groups, optionally establishing an hierarchy between different group of states. It is possible to define states and their groups and methods either via JSON (simpler cases, as for using states per different game scenes) or via C++ (more complex cases, as for the character controller).
- Versioning system: The set of JSON files of an application has as its version controlled by a versioning system that allows entire updates to be applied over the air. Downloaded JSON files can overwrite the existing ones or just apply minor modifications to be merged. All that is synchronized so all the changes that update the application version happens all at once when all the JSON file updates are downloaded. Also, these “JSON file updates” can be automatically generated from the modified version of a JSON, which is compared against original one for taking only the changes to light update file.
- Timer and Scheduler Systems: Timer objects support variable or fixed time steps and may be hierarchically linked to others for pausing and scaling. Any JC++ object method may be subscribed to these timer objects, working as update methods associated to that timer. Scheduler objects subscribes to Timer objects and accept subscriptions from JC++ object methods for triggered them at the moment we specify.
- Task Loader: The “Task Loader” simplifies the process of breaking heavy JC++ methods in several smaller ones, in such way, heavy tasks can be distributed across several frames accordingly to the individual spare time of each frame. This allows operations that would normally require a multithreading environment to be executed on the same thread of the game logic.