@kognifai/cogsengine
    Preparing search index...

    Developing a new Cogs extension

    Let's create a new extension named MyPackage.

    Create a new folder called Cogs.Core/Extensions/MyPackage with the following sub-structure:

    MyPackage/
    ├── Data
    │   ├── Materials
    │   │   └── MyPackageMaterial.material
    │   └── Shaders
    │       ├── MyPackagePS.es30.glsl
    │       ├── MyPackagePS.hlsl
    │       ├── MyPackageVS.es30.glsl
    │       └── MyPackageVS.hlsl
    └── Source
        ├── Components
        │   ├── MyPackageComponent.cpp
        │   └── MyPackageComponent.h
        └── MyPackageExtension.cpp
    

    In this folder, create a file named ``MyPackageExtension.cpp`, which includes:

    #include 
    #include 
    #include 
    #include 
    #include 
    

    Add a new struct with the name of your extension that inherits from public Extension. This must be in Cogs::Core namespace. The constructor should register add the function in the extension registry, by calling ExtensionRegistry::add the struct should override at least three functions;

      bool initializeStatic();
      bool initialize(Context *context);
      const char *getExtensionKey();
    

    Example:

    namespace Cogs::Core
    {
        struct MyPackageExtension : Extension
        {
            MyPackageExtension() { ExtensionRegistry::add(this, COGS_CORE_VERSION_STRING); }
    
            bool initializeStatic() override;
            bool initialize(Context* context) override; 
            const char* getExtensionKey() const override { return "MyPackage"; }
    
        } MyPackageExtension;
    }
    

    NOTE: The extension-struct definition shall not be defined in a MyPackageExtension.h (if such a file is added). This is due to how the API is exported. All function defined in the header file must be plain C-definitions which can be easily exported.

    If the newly created extension requires additional resource files, e.g., shaders, materials or textures, these will be compiled in a zip file that needs to be registered runtime. An example for a web enabled resource loading follows. Note that this will allow desktop version to load the resources directly from disk when available, enabling testing of shaders without recompiling. In initializeStatic add:

        const std::string resourceArchive = "Cogs.Core.Extensions.MyPackage.zip";
    #ifdef EMSCRIPTEN
        bool resourceArchiveAdded = false;
        auto manifest = getResourceManifest(context);
        for (auto& item : manifest) {
            if (item.find(resourceArchive) != std::string::npos) {
                context->resourceStore->addResourceArchive(item);
                resourceArchiveAdded = true;
                break;
            }
        }
        if (!resourceArchiveAdded) {
            context->resourceStore->addResourceArchive(resourceArchive);
        }
    #else
        context->resourceStore->addSearchPath("../Extensions/MyPackage/Data/Materials/");
        context->resourceStore->addSearchPath("../Extensions/MyPackage/Data/Shaders/");
        context->resourceStore->addResourceArchive(resourceArchive);
    #endif
    

    The initializeStatic function of the extension should register any component types the extension uses.

    Example:

    bool Cogs::Core::CoolName::MyPackageExtension()
    {
      MyPackageComponent::registerType();
      return true;
    }
    

    Remove the components from Source/Context.cpp and Source/Types.cpp.

    If the extension provides any Entities to Cogs it should define them in the initialize function by calling readEntityDefinition(), and register any extension system, the extension provides.

    Any additional initialization required by the extension should also be performed here. Failures should be indicated by returning false.

    Call ExtensionRegistry::registerExtensionSystem<SystemType>(), like so:

    bool Cogs::Core::CoolName::initialize(Context *context)
    {
      readEntityDefinition(R"(
      {
        "name": "MyEntity",
        "description": "",
        "components": ["TransformComponent", "SceneComponent", "MyPackageComponent"]
      })", context->store);
    
      ExtensionRegistry::registerExtensionSystem(context, SystemPriority::Default, 16);
      return true;
    }
    

    See existing extension for how to implement a custom System handling components of one type in an extension.

    List all components, types and entities in a new file Cogs.Core/Projects/CodeGenerator/Data/CodeGenerator.MyPackage.config.json.

    Example:

    {
    "key": "MyPackage",
    "name": "Cogs.Core.Extensions.MyPackage",
    "path": "Extensions/Kongsberg.Cogs.Extensions.MyPackage",
    "types": { },
    "namespace": "Kongsberg.Cogs.Extensions.MyPackage",
    "components": [
    "MyPackageComponent"
    ],
    "entities": [
    "MyEntity"
    ]
    }

    Consider if the extension shall be available in Cogs.js.

    Update the code generator json files Projects/CodeGenerator/Data/CodeGenerator.CSharp.config.json, CodeGenerator.js.config.json, and CodeGenerator.Native.config.json, by adding a reference to the newly created config file in the extensions section.

    To update documentation for the code generation doxygen must be installed (see Cogs/Cogs.Core/Documentation/CreateDocset.bat) and a full release build of Cogs must be run on windows. Check generated code to ensure comments on component modules are included.

    Before using anything from the extension, make sure the extension is loaded. Do not load other extension before usage. E.g. not in extension initialize() method.

    if (!::loadExtensionModule("Cogs.Core.Extensions.MyPackage")) {
      return;
    }
    

    Extensions should be optional to build, and we need to create options to build them. Furthermore, the build scrips should be updated to build all extensions by default.

    Add the option to Build/Options.lua

    newoption {
      trigger = "MyPackage",
      description = "Enable the MyPackage extension."
    }
    

    Build/premake5.lua must be updated with a block removing the extension example code if not built.

    if not _OPTIONS["mypackage"] then
          removefiles { "../Demos/Examples/999-myPackageExample.cpp"}
        end
    

    Add the project to Build/ExtensionProjects.lua. This is performed by different premake macros depending on if the extension packages resources or not. Example of an extension with resource package:

    if _OPTIONS["MyPackage"] then
      RegisterExtensionProjectWithPackage("MyPackage", {precompiled = false} )
    end
    

    NOTE: There is a file called Extensions.lua. This can be ignored for now.

    Search for usage of other extensions to find all occurrences in the Cogs meta repos, for example: --potree - check all files found. There are two types of extensions built. With and without resources. Check build pattern for another extension of same kind when adding build for your extension.

    Add the compile option to line 32 in /Build/azure-pipelines.win.yaml which currently looks like this:

    Cogs.Core.Configuration: '--assimp --audio --casing --codegenerator --cuda --curl --curtainview --dime --echosounder --multiphaseflow --nvenc --physics --potree --rationalreducer --seacurrents --streaming --terrain --tinyobjloader --vectorfield --video --SRT --volumetric --MyPackage' 
    

    ..and similarly to line 19 in Build/azure-pipelines.linux.yaml

    repeat the same process in line 16 Build/azure-pipelines.macos.yaml

    The build script in Cogs.js implicitly builds Cogs.Core by calling /Cogs.Build/Tools/premake5 install --HeadersOnly, which is different from how Cogs.Core is build normally. Extensions that require resource zip files musy update Build/Install.lua, by adding the following lines at the appropriate places:

    local MyPackageResourceFile = "Cogs.Core.Extensions.MyPackage.zip"
    os.executef("%s a -r %s ../Extensions/MyPackage/Data/*.*", zipTool, MyPackageResourceFile)
    os.executef("./PackageExtension.sh MyPackage")
    copyifchanged(MyPackageResourceFile, path.join(path.join(cogsdir, "bin"), MyPackageResourceFile))
    

    Build file: Build/azure-pipelines.net.win.yml.

    Build.cmd, where the other extensions are added to buildPremake call ~line 115:

    call :buildPremake Cogs.Core\Build Cogs.Core  %PROJECT_OPTIONS%" --SomeExtension --someOtherExtension --MyPackage
    

    Build.sh, where the other extensions are specified with buildPremake call ~line 82:

    buildPremake Cogs.Core/Build Cogs.Core "--SomeExtension --SomeOtherExtension --MyPackage ..."
    

    Cogs.Core/Build/azure-pipelines.win.yaml, where you see other extensions with

    Cogs.Core.Configuration: "--SomeExtension ...": Cogs.Core.Configuration: '--SomeExtension --SomeOtherExtension --MyPackage'
    

    Cogs.Core/Build/azure-pipelines.linux.yaml: same as azure-pipelines.win.yaml

    Cogs.js Codegen will create an extension definition file under Source/Extensions

    Extension entities are created same way as other entities. Example:

    const myEntity = runtime.MyEntity.create("myEntity1");
    

    Similarly to Cogs.Core, one must create build option in Build/Options.lua by adding:

    newoption {
      trigger = "MyPackage",
      description = "Enable MyPackage extension."
    }
    

    Then add the build option to the extensions list in Build\Build.sh in alphabetical order ~line 143

    ALL_EXTENSIONS="--axiscube --badgeset --casing --curtainview --drilling --heightmap --image360 --ktx2 --multiphase --ogc3dtiles --physics --potree --proceduralsky --reservoir --seacurrents --texatlas --vectorfield --welllog"
    
    

    Build/Premake5.lua must be updated with a block describing the extension, replace axiscube for the name of your extension

    if _OPTIONS["axiscube"] then
      project "Cogs.Core.Extensions.AxisCube"
        CommonCPPProjectSettings(false)
        kind "SharedLib"
        language "C++"
        targetname "Cogs.Core.Extensions.AxisCube"
        defines { "COGS_EXTENSIONS_AXISCUBE" }
        includedirs {
          "../../Cogs.Core/Extensions/AxisCube/Source",
          "../../Cogs.Core/Source",
          "$(COGSDIR)/include/Cogs.Foundation",
          "$(COGSDIR)/include/Cogs.Rendering",
          "../../Libraries/RapidJSON/include",
        }
        files {
          "../../Cogs.Core/Extensions/AxisCube/Source/**.*",
        }
    
      if _OPTIONS["dynamiclinking"] then
        targetdir "../dist"
        targetextension ".wasm"
        linkoptions { "-s SIDE_MODULE=2" }
      end
    end
    

    And added to the linking options

      if _OPTIONS["mypackage"] then
         links { "Cogs.Core.Extensions.MyPackage" }
      end
    ```text
    
    If the extension includes generation of a zip file with resources, also update `Build/Build.sh`:
    
    ```sh
    cp -v ../../Cogs.Core/Build/Cogs.Core.Extensions.ProceduralSky.zip ../dist
    

    and Build/Install.lua

    if _OPTIONS["mypackage"] then
      copyifchanged(path.join(bindir, "Cogs.Core.Extensions.MyPackage.zip"), path.join(distDir, "Cogs.Core.Extensions.MyPackage.zip"))
    end
    

    Remember to add your extension to the declared array of extensions in Main.js line ~498 extensions: ["AxisCube", "BadgeSet", "Casing", ....] Existing applications and demos may work as before with minor modifications. Before using the extension it must be loaded. Example:

    if (!runtime.loadExtensionModule("MyPackage")) {
    return;
    }

    Moving code from core functionality to an extension of a project named MyPackage. Please replace MyPackage with the name of your extension everywhere.

    Create a new folder called Cogs.Core/Extensions/MyPackage. Copy any source or data files from their original location to a new structure as defined in the previous chapter.

    See previous chapter.

    Remove the components from Source/Context.cpp and Source/Types.cpp when the moved extension is registered as specified in the previous chapter.

    Move the definition of any entities from where they are, possibly /Source/EntityDefinitions.cpp to the initialize function.

    Update include statements to reflect their new location, Example:

    #include "Entities/MyPackage/MyEntity.h"
    

    Before using anything from the extension, make sure the extension is loaded.

    if (!::loadExtensionModule("Cogs.Core.Extensions.MyPackage")) {
      return;
    }