.. _tutorial_integration_cpp_class_adapters: .. index:: single: Tutorial; C++ Integration; Class Adapters ======================================= C++ Integration: Class Adapters ======================================= This tutorial shows how to let daslang classes **derive from a C++ abstract base class** and have C++ call their virtual methods seamlessly. This is the key pattern for game object systems where scripts define behavior but C++ owns the update loop. Topics covered: * The 3-layer adapter pattern (base → generated adapter → bridge) * Pre-generated class adapter via ``daslib/cpp_bind`` * ``compileBuiltinModule`` with XDD-embedded ``.das.inc`` files * RTTI (``StructInfo *``) for adapter construction * ``getDasClassMethod`` / ``das_invoke_function`` for virtual dispatch * Virtual dispatch across the C++/daslang boundary Architecture ============ The pattern uses three layers: 1. **C++ base class** (``BaseClass``) — pure virtual interface that C++ code programs against. 2. **Generated adapter** (``TutorialBaseClass``) — generated by ``daslib/cpp_bind``'s ``log_cpp_class_adapter``. For each virtual method, it provides ``get_`` (checks if the daslang class overrides the method) and ``invoke_`` (calls it via ``das_invoke_function``). 3. **Dual-inheritance bridge** (``BaseClassAdapter``) — inherits both the real C++ class and the generated adapter. When C++ calls ``obj->update(dt)`` through the vtable, the bridge checks for a daslang override and invokes it. Prerequisites ============= * Tutorial 08 (:ref:`tutorial_integration_cpp_methods`) — binding methods. * Tutorial 10 (:ref:`tutorial_integration_cpp_custom_modules`) — custom modules. * Understanding of ``ManagedStructureAnnotation`` and ``das_invoke_function``. Layer 1: C++ base class ======================== A simple abstract interface that C++ code iterates over: .. code-block:: cpp class BaseClass { public: virtual ~BaseClass() = default; virtual void update(float dt) = 0; virtual float3 getPosition() = 0; }; C++ maintains a list of ``shared_ptr`` objects and calls ``update`` / ``getPosition`` polymorphically. Layer 2: Generated adapter ============================ The adapter is generated by ``daslib/cpp_bind`` (see ``log_cpp_class_adapter``). It lives in a ``.inc`` file that you ``#include`` in your C++ source: .. code-block:: cpp #include "19_class_adapters_gen.inc" The generated code provides: * ``TutorialBaseClass`` — stores ``StructInfo`` and method lookup data * ``get_update(classPtr)`` — returns the ``Func`` if the daslang class overrides ``update``, or ``nullptr`` if it does not * ``invoke_update(ctx, fn, classPtr, dt)`` — calls the override via ``das_invoke_function::invoke`` Layer 3: Bridge class ====================== The bridge inherits both ``BaseClass`` (for the vtable) and ``TutorialBaseClass`` (for daslang method lookup): .. code-block:: cpp class BaseClassAdapter : public BaseClass, public TutorialBaseClass { public: BaseClassAdapter(char * pClass, const StructInfo * info, Context * ctx) : TutorialBaseClass(info), classPtr(pClass), context(ctx) {} void update(float dt) override { if (auto fn = get_update(classPtr)) { invoke_update(context, fn, classPtr, dt); } } float3 getPosition() override { if (auto fn = get_get_position(classPtr)) { return invoke_get_position(context, fn, classPtr); } return float3(0.0f); } protected: void * classPtr; Context * context; }; Module with XDD-embedded daslang ==================================== The abstract base class definition lives in a ``.das`` file that is embedded into C++ using XDD (a CMake macro that converts files to byte arrays). The module loads it with ``compileBuiltinModule``: .. code-block:: cpp #include "class_adapters_module.das.inc" class Module_Tutorial19 : public Module { public: Module_Tutorial19() : Module("tutorial_19") { ModuleLibrary lib(this); lib.addBuiltInModule(); addBuiltinDependency(lib, Module::require("rtti")); addExtern(*this, lib, "add_object", SideEffects::modifyExternal, "addObject"); compileBuiltinModule("class_adapters_module.das", class_adapters_module_das, sizeof(class_adapters_module_das)); } }; REGISTER_MODULE(Module_Tutorial19); The daslang side ================= Scripts derive from the exposed abstract class and use ``def override`` to provide implementations: .. code-block:: das options gen2 require tutorial_19 require rtti class ExampleObject : TutorialBaseClass { position : float3 speed : float def override update(dt : float) : void { position.x += speed * dt } def override get_position() : float3 { return position } } [export] def test { var obj = new ExampleObject() obj.position = float3(0.0, 0.0, 0.0) obj.speed = 10.0 unsafe { add_object(addr(*obj), class_info(*obj), this_context()) } let avg = tick(0.5) print("After tick(0.5): avg position = ({avg.x}, {avg.y}, {avg.z})\n") } Build & run =========== .. code-block:: bash cmake --build build --config Release --target integration_cpp_19 bin/Release/integration_cpp_19 Expected output:: After tick(0.5): avg position = (5, 0, 0) .. seealso:: Full source: :download:`19_class_adapters.cpp <../../../../tutorials/integration/cpp/19_class_adapters.cpp>`, :download:`19_class_adapters.das <../../../../tutorials/integration/cpp/19_class_adapters.das>`, :download:`class_adapters_module.das <../../../../tutorials/integration/cpp/class_adapters_module.das>`, :download:`19_class_adapters_gen.inc <../../../../tutorials/integration/cpp/19_class_adapters_gen.inc>` Previous tutorial: :ref:`tutorial_integration_cpp_dynamic_scripts` Next tutorial: :ref:`tutorial_integration_cpp_standalone_contexts` Related: :ref:`tutorial_integration_cpp_methods`, :ref:`tutorial_integration_cpp_custom_modules`