Velk is a C++17 component object model library with interface-based polymorphism, typed properties with change notifications, events, compile-time metadata with runtime introspection, and a plugin system for modular extension via shared libraries.
Velk includes dense, cache-friendly object storage via hives, promise/future pairs with .then() chaining, and deferred execution for batching property writes and function calls.
Velk is designed to be built as a shared library (DLL on Windows, .so on Linux). All runtime implementations live inside the shared library, while consumers only depend on public headers containing abstract interfaces and header-only templates. This means the internal implementation can evolve without recompiling consumer code, multiple modules can share a single type registry and object factory, and ABI compatibility is maintained through stable virtual interfaces.
// Define an interface
class IWidget : public Interface<IWidget> {
public:
VELK_INTERFACE(
(PROP, float, width, 100.f),
(EVT, on_clicked),
(FN, void, reset)
)
};
// Implement, register, create, use
class Widget : public ext::Object<Widget, IWidget> {
void fn_reset() override { width().set_value(0.f); }
};
// Register type
auto& instance = velk::instance();
instance.type_registry().register_type<Widget>();
// Instantiate and use
auto w = instance.create<IWidget>(Widget::static_class_id());
w->width().set_value(42.f);
w->width().on_changed().add_handler([](){ /*...*/ });
w->on_clicked().add_handler([]() { /*...*/ });
velk::invoke_function(w->reset());Note: Claude Code skill for Velk usage can be found in docs/claude-skill/velk. Copy the files under .claude/skills/velk to enable /velk command in Claude CLI.
| Feature | Description |
|---|---|
| ABI stable | Interface-based contracts with properties, events, and functions. Consumers depend only on public headers; internals can change without recompilation |
| Zero dependencies | Pure C++17 with platform headers only |
| Compile-time metadata | Declare properties, events, and functions with Typed properties with change notifications, property bindings, multicast events, virtual functions with typed parameters and return values, promise/future chaining, and deferred execution |
| Extensible | Plugin registry for inline or DLL-based plugins with declarative dependencies and multi-plugin bundles Type registry for UID-based creation and runtime introspection Attachments for injecting capabilities (decorators, custom behaviors) into objects at runtime External hierarchy that manages parent/child trees without per-object overhead |
| Hive storage | Dense, cache-friendly containers with slot reuse, zombie lifecycle, and automatic page management |
| Performance-focused | Inline state structs, lazy instantiation, single-indirect-call dispatch, and direct state access with zero overhead No RTTI or exceptions, builds with |
| Scriptable | Bindings, functions, event handlers optionally in JavaScript. |
| C API | Flat C API (velk_c.dll) with opaque typed handles for FFI consumption from any language |
| Document | Description |
|---|---|
| Quick start | Define an interface, implement it, create objects, and use typed accessors |
| Guide | How to declare interfaces, work with properties, functions, events, and futures |
| Architecture | How the four layers fit together, type hierarchy, and ABI stability |
| Hive | Storing objects in dense, cache-friendly containers |
| Plugins | Extending Velk with inline or DLL-based plugins |
| Performance | Benchmark numbers, memory layout, and object sizes |
| Resources | URI-based resource access with pluggable protocol handlers |
| Advanced | Any types and value chains, writing metadata by hand, shared_ptr internals |
| C API | Flat C API for FFI: handles, ref counting, property access, events |
| Built-in plugins | |
| Importer | JSON scene import with extensible collections |
| Animator | Implicit animations and transitions for properties |
| Scripting | JavaScript scripting via QuickJS-ng: expression bindings and event handlers |
| Tracy | Tracy profiler integration via the perf sink interface |
velk/
CMakeLists.txt
README.md This file
benchmark/ Benchmarks (Google Benchmark)
demo/ Feature demonstration
docs/ Documentation
architecture.md Layers, headers, type hierarchy, key types
guide.md Extended usage guide
hive.md Dense object storage and hive registry
performance.md Performance and memory usage
plugins.md Plugin system: writing, loading, dependencies, bundles
resources.md URI-based resource access with pluggable protocols
advanced.md Any types, value chains, manual metadata, shared_ptr internals
plugins/
importer.md Importer plugin: JSON import, extensions, format reference
animator.md Animator plugin: transitions, easing, interpolators
scripting.md Scripting plugin: JavaScript expression bindings, event handlers
tracy.md Tracy plugin: profiler integration via the perf sink
velk/
include/ Public API (consumers depend only on these headers)
interface/ Abstract interfaces (ABI contracts)
ext/ CRTP helpers and template implementations
api/ User-facing typed wrappers
src/ DLL internals (compiled into velk.dll)
c_api/ Flat C API (compiled into velk_c.dll)
plugins/ Built-in plugins (compiled into separate dlls)
test/ Unit tests (GoogleTest)
Requires CMake 3.14+ and a C++17 compiler with a C compiler (for QuickJS). Tested with MSVC 2019.
cmake -B build
cmake --build build --config ReleaseOutput:
build/bin/Release/velk.dll(shared library)build/bin/Release/velk_c.dll(C API shared library)build/bin/Release/velk_importer.dll(shared library)build/bin/Release/velk_animator.dll(shared library)build/bin/Release/velk_js.dll(shared library)build/bin/Release/demo.exe(demo)build/bin/Release/tests.exe(unit tests)build/bin/Release/benchmarks.exe(benchmarks)
Unit tests use GoogleTest 1.14.0 (vendored in test/third_party/).
Benchmarks use Google Benchmark 1.9.1 (vendored in benchmark/third_party/).
Both are extracted into the build directory automatically during CMake configuration.
Use VELK_INTERFACE to declare properties, events, and functions. This generates both a static constexpr metadata array and typed accessor methods.
#include <interface/intf_metadata.h>
class IMyWidget : public Interface<IMyWidget>
{
public:
// Static interface metadata:
VELK_INTERFACE(
(PROP, float, width, 100.f), // Read-write float property, default value 100.f
(PROP, float, height, 100.f),
(RPROP, int, id, 0), // Read-only int property, default value 0
(ARR, uint32_t, layers, 1u, 2u, 3u), // Array uint32_t property (array of values), default value {1u, 2u, 3u}
(EVT, on_clicked), // Event
(FN, void, reset), // Function without arguments
(FN, void, resize, (float, w), (float, h)) // Function with 2 arguments
)
// Regular pure virtual function, part of the interface but not metadata.
virtual void paint() = 0;
};Object automatically collects metadata from all listed interfaces and provides the IMetadata implementation. Override fn_<Name> to provide function logic.
#include <velk/ext/object.h>
class MyWidget : public ext::Object<MyWidget, IMyWidget>
{
// Implementations of the IMyWidget static metadata-defined functions "reset" and "resize"
void fn_reset() override {
}
void fn_resize(float w, float h) override {
width().set_value(w); // width property as defined in VELK_INTERFACE
height().set_value(h);
}
// Implementation of the pure virtual non-metadata function defined in IMyWidget
void paint() override {}
};Multiple interfaces are supported:
class ISerializable : public Interface<ISerializable>
{
public:
VELK_INTERFACE(
(PROP, velk::string, name, ""),
(FN, void, serialize)
)
};
class MyWidget : public ext::Object<MyWidget, IMyWidget, ISerializable>
{
// Implementations for the functions defined in IMyWidget and ISerializable static metadata
void fn_reset() override { /* ... */ } // zero-arg
void fn_resize(float w, float h) override { /* ... */ } // typed args
void fn_serialize() override { /* ... */ } // from ISerializable
// Non-metadata paint() from IMyWidget
void paint() override {}
};auto& s = instance(); // global IVelk
s.type_registry().register_type<MyWidget>(); // register factory
auto widget = s.create<IObject>(MyWidget::static_class_id()); // create by UIDauto widget = s.create<IMyWidget>(MyWidget::static_class_id());
if (widget) {
auto wp = iw->width();
wp.set_value(42.f); // set property
float w = wp.get_value(); // read property
Event clicked = iw->on_clicked(); // get event handle
clicked.add_handler([]() { std::cout << "clicked"; }); // add event handler
Function reset = iw->reset().invoke(); // get function handle
}Static metadata is available from Velk without creating an instance:
if (auto* info = instance().type_registry().get_class_info(MyWidget::static_class_id())) { // lookup by UID
for (auto& i : info->interfaces) { // enumerate interfaces
// i.uid, i.name
}
for (auto& m : info->members) { // enumerate members
// m.name, m.kind, m.interfaceInfo
}
}Runtime metadata is available through IMetadata on any instance:
auto widget = s.create(MyWidget::static_class_id());
if (auto* meta = interface_cast<IMetadata>(widget)) { // query interface for runtime introspection
auto prop = meta->get_property("width"); // lookup by name
auto event = meta->get_event("on_clicked"); // lookup by name
auto func = meta->get_function("reset"); // lookup by name
}Properties are backed by inline State structs generated by VELK_INTERFACE. Use read_state and write_state for direct struct access with automatic change notifications. Both return a handle that converts to false if the interface is not implemented.
auto widget = s.create<IObject>(MyWidget::static_class_id());
auto* iw = interface_cast<IMyWidget>(widget);
// Read state directly
if (auto reader = read_state<IMyWidget>(iw)) {
float w = reader->width; // 100.f (default)
}
// Write state, fires on_changed for instantiated properties when writer goes out of scope
if (auto writer = write_state<IMyWidget>(iw)) {
// struct IMyWidget::State { // generated by VELK_INTERFACE
// float width { 100.f };
// float height { 100.f };
// int id { 0 };
// velk::vector<uint32_t> layers { 1u, 2u, 3u };
// }
writer->width = 200.f;
writer->height = 50.f;
writer->layers.push_back(4u);
} // ~StateWriter notifies listeners
iw->width().get_value(); // 200.f