# ApiGear Documentation > ApiGear generates native SDKs from stateful API definitions. Define interfaces once using ObjectAPI, then generate production-ready code for C++, Qt, Unreal Engine, or Python. ## blog Blog - [Blog](/blog.md): Blog ### ai-meets-api-design-mcp Connect ApiGear to your AI coding assistant via MCP and generate complete SDKs from API descriptions — right from your editor. - [AI Meets API Design: MCP-Powered SDK Generation](/blog/ai-meets-api-design-mcp.md): Connect ApiGear to your AI coding assistant via MCP and generate complete SDKs from API descriptions — right from your editor. ### api-surface Overview of a typical API surface for objects in an IDL. - [API surface for objects](/blog/api-surface.md): Overview of a typical API surface for objects in an IDL. ### archive Archive - [Archive](/blog/archive.md): Archive ### authors - [Authors](/blog/authors.md) ### tags - [Tags](/blog/tags.md) - [One post tagged with "ai"](/blog/tags/ai.md) - [One post tagged with "android"](/blog/tags/android.md) - [One post tagged with "api"](/blog/tags/api.md) - [One post tagged with "code-generation"](/blog/tags/code-generation.md) - [One post tagged with "idl"](/blog/tags/idl.md) - [One post tagged with "ipc"](/blog/tags/ipc.md) - [One post tagged with "java"](/blog/tags/java.md) - [One post tagged with "jni"](/blog/tags/jni.md) - [One post tagged with "mcp"](/blog/tags/mcp.md) - [One post tagged with "sdk"](/blog/tags/sdk.md) - [One post tagged with "unreal"](/blog/tags/unreal.md) ### unreal-android-jni-bridge Generate the Android Messenger plumbing, Parcelable payloads, and JNI bindings for an Unreal Engine app from one API definition — using the Java and Unreal Engine templates together. - [ApiGear on Android: Bridging Unreal Engine and Java with Generated JNI](/blog/unreal-android-jni-bridge.md): Generate the Android Messenger plumbing, Parcelable payloads, and JNI bindings for an Unreal Engine app from one API definition — using the Java and Unreal Engine templates together. ## template-cpp14 ### docs - [Features](/template-cpp14/docs/features.md): Overview of the C++14 template features for the ApiGear code generator: generated interfaces and data types, stub implementations, networking adapters, and traffic monitoring. - [API Feature](/template-cpp14/docs/features/api.md): How the ApiGear C++14 template generates API code from ObjectAPI definitions — interfaces, structs, and enums for your C++14 SDK. - [Conan package manager](/template-cpp14/docs/features/conan.md): Package the generated ApiGear C++14 SDK with the Conan C++ package manager for clean dependency management and reuse. - [Monitor](/template-cpp14/docs/features/monitor.md): Traffic monitoring in the ApiGear C++14 template: trace interface properties, methods, and signals from your C++14 SDK in ApiGear Studio. - [MQTT](/template-cpp14/docs/features/mqtt.md): MQTT networking in the ApiGear C++14 template: expose and consume C++14 interfaces over MQTT using generated client and server adapters. - [OLink](/template-cpp14/docs/features/olink.md): OLink (ObjectLink) networking in the ApiGear C++14 template: connect C++14 interface clients and services over the ObjectLink protocol with built-in monitoring. - [Stubs](/template-cpp14/docs/features/stubs.md): Generated stub implementations in the ApiGear C++14 template: ready-to-extend default classes for your interfaces, ideal for prototyping and testing C++14 services. - [Template C++ 14 (Maintenance)](/template-cpp14/docs/intro.md): Maintenance-mode C++14 template for the ApiGear code generator. Generate C++14-compatible SDKs with OLink and MQTT networking; new projects should use the C++17 template. - [Quick-Start](/template-cpp14/docs/quickstart.md): Quick-start guide for the ApiGear C++14 template: install the generator, define an ObjectAPI interface, and generate your first C++14 SDK in minutes. ## template-cpp17 ### docs - [Features](/template-cpp17/docs/features.md): Overview of the C++17 template features for the ApiGear code generator: generated interfaces and data types, stub implementations, networking adapters, and traffic monitoring. - [API Feature](/template-cpp17/docs/features/api.md): How the ApiGear C++17 template generates API code from ObjectAPI definitions — interfaces, structs, and enums for your C++17 SDK. - [Conan package manager](/template-cpp17/docs/features/conan.md): Package the generated ApiGear C++17 SDK with the Conan C++ package manager for clean dependency management and reuse. - [Monitor](/template-cpp17/docs/features/monitor.md): Traffic monitoring in the ApiGear C++17 template: trace interface properties, methods, and signals from your C++17 SDK in ApiGear Studio. - [MQTT](/template-cpp17/docs/features/mqtt.md): MQTT networking in the ApiGear C++17 template: expose and consume C++17 interfaces over MQTT using generated client and server adapters. - [NATS](/template-cpp17/docs/features/nats.md): NATS networking in the ApiGear C++17 template: connect C++17 interface clients and services over the NATS messaging system. - [OLink](/template-cpp17/docs/features/olink.md): OLink (ObjectLink) networking in the ApiGear C++17 template: connect C++17 interface clients and services over the ObjectLink protocol with built-in monitoring. - [Stubs](/template-cpp17/docs/features/stubs.md): Generated stub implementations in the ApiGear C++17 template: ready-to-extend default classes for your interfaces, ideal for prototyping and testing C++17 services. - [Template C++ 17](/template-cpp17/docs/intro.md): Generate modern C++17 SDKs from ObjectAPI definitions with ApiGear: interfaces, structs, stub implementations, plus OLink, NATS, and MQTT networking with built-in monitoring. - [Quick-Start](/template-cpp17/docs/quickstart.md): Quick-start guide for the ApiGear C++17 template: install the generator, define an ObjectAPI interface, and generate your first C++17 SDK in minutes. ## template-java ### docs - [Features](/template-java/docs/features.md): Overview of ApiGear Java template features: API generation, stub implementations, Android service/client IPC, and JNI bridge. - [Android Feature](/template-java/docs/features/android.md): Generate Android Messenger-based service/client architecture for cross-process and cross-app API communication. - [API Feature](/template-java/docs/features/api.md): Generate Java interfaces, structs, enums, and abstract base classes from API definitions. Includes async operations, property change listeners, and ready-state management. - [JNI Bridge Feature](/template-java/docs/features/jnibridge.md): Generate JNI bridge classes for integrating the Java/Android API with native C++ code, such as Unreal Engine. - [Stubs](/template-java/docs/features/stubs.md): Generate ready-to-use stub implementations with property change detection, async operations, and ready-state management. - [Template Java](/template-java/docs/intro.md): Generate Java interfaces, Android service/client IPC, and JNI bridge code from API definitions with the ApiGear code generator. - [Quick-Start](/template-java/docs/quickstart.md): Step-by-step guide to generate Java interfaces and data types from YAML API definitions using the ApiGear code generator. ## template-python ### docs - [Features](/template-python/docs/features.md): Overview of the Python template features for the ApiGear code generator: generated interfaces and data types, stub implementations, networking adapters, and traffic monitoring. - [MQTT](/template-python/docs/features/mqtt.md): MQTT networking in the ApiGear Python template: expose and consume Python interfaces over MQTT using generated client and server adapters. - [Template Python](/template-python/docs/intro.md): Generate Python SDKs from ObjectAPI definitions with ApiGear: typed interfaces, asyncio clients, OLink and MQTT networking, and monitoring for stateful APIs. - [Quick-Start](/template-python/docs/quickstart.md): Quick-start guide for the ApiGear Python template: install the generator, define an ObjectAPI interface, and generate your first Python SDK in minutes. ## template-qt6 ### docs - [Features](/template-qt6/docs/features.md): Overview of the Qt6 C++ template features for the ApiGear code generator: generated interfaces and data types, stub implementations, networking adapters, and traffic monitoring. - [API](/template-qt6/docs/features/api.md): How the ApiGear Qt6 C++ template generates API code from ObjectAPI definitions — interfaces, structs, and enums for your Qt6 C++ SDK. - [Monitor](/template-qt6/docs/features/monitor.md): Traffic monitoring in the ApiGear Qt6 C++ template: trace interface properties, methods, and signals from your Qt6 C++ SDK in ApiGear Studio. - [MQTT](/template-qt6/docs/features/mqtt.md): MQTT networking in the ApiGear Qt6 C++ template: expose and consume Qt6 C++ interfaces over MQTT using generated client and server adapters. - [OLink](/template-qt6/docs/features/olink.md): OLink (ObjectLink) networking in the ApiGear Qt6 C++ template: connect Qt6 C++ interface clients and services over the ObjectLink protocol with built-in monitoring. - [QML Plugin](/template-qt6/docs/features/qmlplugin.md): Generate a QML plugin from ObjectAPI definitions with the ApiGear Qt6 template — use your interfaces directly in QML and Qt Quick applications. - [Stubs](/template-qt6/docs/features/stubs.md): Generated stub implementations in the ApiGear Qt6 C++ template: ready-to-extend default classes for your interfaces, ideal for prototyping and testing Qt6 C++ services. - [Template Qt6](/template-qt6/docs/intro.md): Generate Qt6 C++ and QML SDKs from ObjectAPI definitions with ApiGear: QObject-based interfaces, QML-ready types, OLink and MQTT networking, and built-in monitoring. - [Quick-Start](/template-qt6/docs/quickstart.md): Quick-start guide for the ApiGear Qt6 C++ template: install the generator, define an ObjectAPI interface, and generate your first Qt6 C++ SDK in minutes. ## template-rust ### docs - [Features](/template-rust/docs/features.md): Overview of the Rust template features for the ApiGear code generator: generated interfaces and data types, stub implementations, networking adapters, and traffic monitoring. - [API Feature](/template-rust/docs/features/api.md): How the ApiGear Rust template generates API code from ObjectAPI definitions — interfaces, structs, and enums for your Rust SDK. - [Monitor](/template-rust/docs/features/monitor.md): Traffic monitoring in the ApiGear Rust template: trace interface properties, methods, and signals from your Rust SDK in ApiGear Studio. - [MQTT](/template-rust/docs/features/mqtt.md): MQTT networking in the ApiGear Rust template: expose and consume Rust interfaces over MQTT using generated client and server adapters. - [NATS](/template-rust/docs/features/nats.md): NATS networking in the ApiGear Rust template: connect Rust interface clients and services over the NATS messaging system. - [OLink](/template-rust/docs/features/olink.md): OLink (ObjectLink) networking in the ApiGear Rust template: connect Rust interface clients and services over the ObjectLink protocol with built-in monitoring. - [Stubs](/template-rust/docs/features/stubs.md): Generated stub implementations in the ApiGear Rust template: ready-to-extend default classes for your interfaces, ideal for prototyping and testing Rust services. - [Template Rust](/template-rust/docs/intro.md): Generate Rust SDKs from ObjectAPI definitions with ApiGear: trait-based interfaces, async Tokio clients, OLink, NATS, and MQTT networking, and built-in monitoring. - [Quick-Start](/template-rust/docs/quickstart.md): Quick-start guide for the ApiGear Rust template: install the generator, define an ObjectAPI interface, and generate your first Rust SDK in minutes. ## template-unreal ### docs - [Features](/template-unreal/docs/features.md): Overview of ApiGear Unreal Engine template features: Core API generation, stub implementations, OLink networking, MQTT pub/sub, JNI Android communication, Message Bus IPC, and runtime monitoring. - [API Feature](/template-unreal/docs/features/api.md): Generate Unreal Engine interfaces, structs, and enums from API definitions. Full Blueprint and C++ support with property change notifications and async operations. - [JNI](/template-unreal/docs/features/jni.md): Connect Unreal Engine to Android services via JNI. Adapter and client for cross-platform Unreal Engine and Android communication using the Java template's jnibridge feature. - [Licensing and Author Metadata](/template-unreal/docs/features/licensing.md): Generated plugins include MIT LICENSE files and SPDX headers. Customize author metadata in .uplugin descriptors and copyright notices via the meta field. - [Monitor](/template-unreal/docs/features/monitor.md): Trace API calls, property changes, and signals at runtime. View live debugging data in ApiGear Studio or CLI for Unreal Engine interfaces. - [MQTT](/template-unreal/docs/features/mqtt.md): Connect Unreal Engine to any MQTT broker. Publish and subscribe to topics for IoT, telemetry, and many-to-many communication with non-Unreal peers. - [Message Bus](/template-unreal/docs/features/msgbus.md): Use Unreal Engine's built-in Message Bus for IPC between Unreal applications. Zero-config local communication for editor tools and distributed systems. - [OLink](/template-unreal/docs/features/olink.md): Connect Unreal Engine to remote services via ObjectLink WebSocket protocol. Client and server adapters for cross-platform communication and simulation. - [Stubs](/template-unreal/docs/features/stubs.md): Generate ready-to-use stub implementations as Unreal Engine GameInstance Subsystems. Includes test fixtures and property change notifications. - [Template Unreal Engine](/template-unreal/docs/intro.md): Generate Unreal Engine plugins from API definitions with full Blueprint and C++ support. Create production-ready code with OLink networking, Message Bus IPC, and monitoring. - [Quick-Start](/template-unreal/docs/quickstart.md): Step-by-step guide to generate Unreal Engine plugins from YAML API definitions. Learn to create Blueprint and C++ interfaces with ApiGear code generator. ## docs ApiGear generates native SDKs from stateful API definitions using ObjectAPI. Generate production-ready code for C++, Qt, Unreal Engine, and Python. - [Introduction](/docs.md): ApiGear generates native SDKs from stateful API definitions using ObjectAPI. Generate production-ready code for C++, Qt, Unreal Engine, and Python. ### cli - [Code Generation](/docs/cli/generate.md): Generate SDKs from ObjectAPI definitions using solution files or expert mode. Covers CLI flags, solution file format, features, and error handling. - [Introduction](/docs/cli/intro.md): Install and use the ApiGear CLI to generate production-ready SDKs, simulate APIs, monitor traffic, and integrate with AI assistants via MCP. - [MCP Server](/docs/cli/mcp.md): Use the ApiGear MCP server to expose SDK generation, template management, and spec validation as tools for Claude Code, Cursor, and other AI assistants. - [API Monitoring](/docs/cli/monitor.md): Monitor API traffic in real-time using the ApiGear CLI. View property changes, operation calls, and signals from your generated SDK applications. - [ObjectLink REPL](/docs/cli/olink.md): Use the ObjectLink REPL to connect to servers, link remote objects, invoke methods, set properties, and observe signals interactively over WebSocket. - [Project Management](/docs/cli/project.md): Create, edit, and manage ApiGear projects. Set up API modules, solution files, and simulation scenarios from the command line. - [Scripting Support](/docs/cli/simulate.md): Simulate API backends with JavaScript scripts or YAML scenarios. Test your application against mock data before real services exist. - [SDK Templates](/docs/cli/template.md): Manage SDK templates for code generation. Install from the registry, list cached templates, and search for available templates. ### guide - [Calculator API Demo](/docs/guide/calculator.md): Create and simulate a Calculator API in minutes using ApiGear’s model-driven generator. - [Counter API Demo](/docs/guide/counter.md): Walk through building a coffee counter API with ApiGear. Define an ObjectAPI module, configure a solution, generate code, and simulate the backend. - [Quick Start](/docs/guide/quick-start.md): Generate your first SDK in 5 minutes. Install ApiGear, define an API module, configure a solution file, and generate C++, Qt, Unreal Engine, or Python code. - [Update Strategy](/docs/guide/update-strategy.md): Strategies for managing generated code updates when your API evolves. Understand the force flag, protected files, and merge approaches. ### monitor - [Introduction](/docs/monitor/intro.md): Monitor API usage in real-time during development and testing. Detect anomalies by tracking property changes, operation calls, and signals. - [Monitoring Protocol](/docs/monitor/protocol.md): The API Monitor protocol uses HTTP POST to report property changes, operation calls, and signals to the embedded monitoring server. ### objectapi - [Advanced Concepts](/docs/objectapi/advanced.md): Advanced ObjectAPI concepts including document info blocks, meta annotations for custom code generation, and compact YAML writing format. - [API Foundation](/docs/objectapi/core.md): Learn the ObjectAPI document format, primitive types (bool, int, float, string), arrays, complex types, and cross-module imports. - [API Examples](/docs/objectapi/examples.md): Complete ObjectAPI YAML examples including a Counter, Radio Tuner with structs and enums, and a Weather Station with signals. - [ObjectAPI IDL](/docs/objectapi/idl.md): Write APIs using the ObjectAPI IDL, a concise code-like syntax that auto-converts to YAML. Covers modules, interfaces, structs, enums, and imports. - [Introduction](/docs/objectapi/intro.md): ObjectAPI defines stateful, object-oriented APIs — properties, operations, and signals — and gives designers and engineers a shared language from rapid prototyping to production. - [API Modules](/docs/objectapi/modules.md): Define API modules with interfaces (properties, operations, signals), data structures, and enumerations using the ObjectAPI specification. ### protocols - [HTTP Mapping](/docs/protocols/http/mapping_http.md): Map ObjectAPI interfaces to HTTP endpoints using an RPC model. Covers property, operation, and signal mapping with JSON payloads. - [Protocol Mappings](/docs/protocols/mapping.md): How ApiGear's ObjectAPI relates to OpenAPI, AsyncAPI, gRPC, MQTT, SOME/IP and DDS — and why it generates code that runs over them instead of replacing them. - [ApiGear over MQTT](/docs/protocols/mqtt/intro.md): How ApiGear maps ObjectAPI interfaces onto MQTT v5: brokered pub/sub for properties, signals and operations, shared across the C++, Qt, Python, Rust and Unreal templates. - [ApiGear MQTT Topic Mapping](/docs/protocols/mqtt/mapping.md): The ApiGear MQTT v5 topic-mapping spec: how interface operations, signals and properties map to MQTT topics and JSON payloads, with correlation, retained state and a v3 fallback. - [ApiGear over NATS](/docs/protocols/nats/intro.md): How ApiGear maps ObjectAPI interfaces onto NATS subjects: request/reply for operations, pub/sub for signals and properties, plus connectivity messaging, shared across templates. - [ApiGear NATS Subject Mapping](/docs/protocols/nats/mapping.md): How ApiGear maps ObjectAPI interface operations, signals and properties onto NATS subjects and JSON payloads, with request/reply and connectivity messaging. - [Use Cases](/docs/protocols/objectlink/cases.md): ObjectLink use cases including embedded service sharing, sensor-to-dashboard linking, and UI-to-service layer communication across platforms. - [Error Messages](/docs/protocols/objectlink/errors.md): ObjectLink error message format and error codes. Covers error handling for link, invoke, set-property, and signal operations. - [Introduction](/docs/protocols/objectlink/intro.md): ObjectLink is an IPC protocol that links local objects to remote objects over network connections, synchronizing properties, operations, and signals. - [Lifecycle](/docs/protocols/objectlink/lifecycle.md): Manage the ObjectLink object lifecycle with LINK, INIT, and UNLINK messages. Learn how remote objects are linked and initialized with property state. - [Method Invokation](/docs/protocols/objectlink/methods.md): Invoke methods on remote objects using the ObjectLink protocol. Covers INVOKE and INVOKE_REPLY message format with request IDs. - [Property Synchronization](/docs/protocols/objectlink/properties.md): Synchronize object properties between local and remote objects. Covers SET_PROPERTY and PROPERTY_CHANGE messages for real-time state updates. - [Server Side Signals](/docs/protocols/objectlink/signals.md): Receive server-side event notifications through ObjectLink signals. Signals push events from remote objects to all linked local objects. ### scripting - [JavaScript API](/docs/scripting/backends/api.md): JavaScript API reference for the simulation server. Covers actors, property handling, operation callbacks, signal emission, and timer functions. - [Simulation Examples](/docs/scripting/backends/examples.md): Complete simulation examples including a heating control system with thermostat logic, a ball physics animation, and a vehicle dashboard with multiple services. - [Service Simulation](/docs/scripting/backends/intro.md): Simulate API services with JavaScript to test client code without real backends. Create virtual objects with state, behavior, and change notifications. - [Simulation Protocol](/docs/scripting/backends/protocols.md): How the simulation framework uses the ObjectLink protocol to serve simulated API responses to clients over network connections. - [Simulation Scenarios](/docs/scripting/backends/scenario.md): Write simulation scenarios in JavaScript to define API endpoint behavior. Configure actors with properties, operation handlers, and signal emitters. - [Generator Based Simulation](/docs/scripting/backends/solution.md): Auto-generate simulation scenarios from API definitions. Quickly scaffold mock backends for large APIs without writing simulation code by hand. - [Examples](/docs/scripting/clients/examples.md): JavaScript client script examples that connect to remote services. Includes counter increment and heating system control scenarios. - [Scripted Clients](/docs/scripting/clients/intro.md): Use JavaScript client scripts to trigger operation sequences and data changes on remote services via the ObjectLink protocol. - [Scripting](/docs/scripting/intro.md): Use JavaScript scripting to simulate API backends and drive API clients. Test, prototype, and demo without compiled code or deployed services. ### sdk - [SDK Templates](/docs/sdk/intro.md): Browse available SDK templates for code generation. Includes C++17, Qt 6, Unreal Engine, Python, Go, Rust, and TypeScript with protocol and monitoring support. - [Filter Reference](/docs/sdk/maker/filters.md): Reference for template filters that transform text in SDK templates. Covers case conversion (snake, camel, pascal), path helpers, and string utilities. - [Introduction](/docs/sdk/maker/intro.md): Create and customize SDK templates for ApiGear code generation. Adapt existing templates or build new ones for your technology stack. - [Rules Document](/docs/sdk/maker/rules.md): Define code generation rules with features, file output targets, and template-to-source mappings. Control which files are generated and when. - [Symbol Reference](/docs/sdk/maker/symbols.md): Reference for all symbols available in SDK templates. Covers System, Module, Interface, Structure, Enumeration, and their properties. - [Template Primer](/docs/sdk/maker/template.md): Learn the Go text/template syntax used in ApiGear SDK templates. Covers variables, loops, conditionals, functions, and template composition. - [Quick Start](/docs/sdk/maker/tutorial.md): Step-by-step tutorial to create an SDK template that generates TypeScript interfaces from ObjectAPI definitions. Covers rules, templates, and solution setup. ### studio - [Introduction](/docs/studio/intro.md): ApiGear Studio is a desktop application for designing APIs, generating SDKs, running simulations, and monitoring API traffic in one integrated environment. - [Project Management](/docs/studio/project.md): Create, open, and manage API projects in ApiGear Studio. Organize modules, solutions, and scenarios within the project workspace. - [Quick Start](/docs/studio/quickstart.md): Get started with ApiGear Studio. Create a project, define API modules, configure code generation, and monitor API traffic in the desktop IDE. --- # Full Documentation Content ## [ApiGear on Android: Bridging Unreal Engine and Java with Generated JNI](/blog/unreal-android-jni-bridge.md) April 30, 2026 · 11 min read ![Wolfgang Bremer](https://avatars.githubusercontent.com/u/11640828?v=4) Wolfgang Bremer Co-Founder [](https://github.com/w4bremer "GitHub") You already have an Android app. It owns the data — vehicle telemetry, sensor state, transactional records — and exposes it through a Java or Kotlin service layer. Now product wants a richer surface on top: a 3D instrument cluster, an operator dashboard, a kiosk experience that needs more than the standard Android UI toolkit gives you. The natural answer is to add an Unreal Engine app as a rendering tier — a second process that draws the HMI and reaches back into your existing Java service for the data it visualizes. That's a cross-process Android problem with a Java service host on one end and a C++ engine consumer on the other. Bind a `Service`, write a `Messenger` handler, define a `Parcelable` for every payload, hand-roll a JNI surface so the engine's C++ can call into the Java client, then keep the method signatures in sync forever. ApiGear's new [Java template](/template-java/docs/intro.md) ships the Android counterpart to the Unreal Engine template's new [JNI feature](/template-unreal/docs/features/jni.md), which generates both a JNI client (for consuming an Android service) and a JNI adapter (for exposing one). Define your interface once, generate both sides, and the Messenger plumbing, parcels, and JNI bindings appear together. This post walks through the realistic case — *Android app hosts the service, UE app consumes it* — and shows what comes out of the generator. **Tags:** * [unreal](/blog/tags/unreal.md) * [android](/blog/tags/android.md) * [java](/blog/tags/java.md) * [jni](/blog/tags/jni.md) * [ipc](/blog/tags/ipc.md) [**Read more**](/blog/unreal-android-jni-bridge.md) --- # AI Meets API Design: MCP-Powered SDK Generation March 2, 2026 · 3 min read ![Wolfgang Bremer](https://avatars.githubusercontent.com/u/11640828?v=4) Wolfgang Bremer Co-Founder [](https://github.com/w4bremer "GitHub") What if you could describe an API in plain English and your AI assistant would design the interface, validate it, and generate a production-ready SDK — all without leaving your editor? That's exactly what happens when you connect ApiGear to your AI coding assistant via MCP. No spec syntax to learn, no CLI commands to memorize. You describe what you need, and your assistant handles the rest — from API design to generated code. ## MCP in 30 Seconds[​](#mcp-in-30-seconds "Direct link to MCP in 30 Seconds") [MCP](https://modelcontextprotocol.io/) (Model Context Protocol) is an open standard that lets AI assistants call external tools directly. Think of it as giving your AI hands to work with real developer tools. ApiGear ships an MCP server that exposes its full code generation pipeline — so any MCP-compatible assistant (Claude, Cursor, and others) can validate specs, pick templates, and generate SDKs on your behalf. ## From Plain English to a Working SDK[​](#from-plain-english-to-a-working-sdk "Direct link to From Plain English to a Working SDK") It starts with a simple request: > "I need a counter API with a count property and increment/decrement operations." Your AI assistant fetches the ApiGear module schema via MCP, understands the structure, and writes a valid API module for you: ``` schema: apigear.module/1.0 name: demo version: "1.0" interfaces: - name: Counter properties: - name: count type: int operations: - name: increment - name: decrement ``` You didn't write that. You didn't need to know the spec format. The assistant looked up the schema, drafted the module, and validated it — all through ApiGear's MCP tools. Now take it one step further: > "Generate a Unreal Engine SDK into the `out/` folder." The assistant selects the right template and generates a fully scaffolded project — interfaces, stubs, build files — without you typing a single command. This is the full loop: natural language to validated API spec to generated SDK. You describe what you want at each step, and the assistant handles the rest. Need to add a new operation? Just say so. Want to switch from Unreal Engine to C++ or Python? Ask. The assistant re-validates and regenerates. ## Set It Up in One Minute[​](#set-it-up-in-one-minute "Direct link to Set It Up in One Minute") Add ApiGear as an MCP server in your AI tool. For Claude Code, it's a single command: ``` claude mcp add apigear -- apigear mcp ``` That's it. Your AI assistant now has access to ApiGear's full generation pipeline — spec validation, template browsing, and SDK generation — all through natural conversation. See the [MCP Server documentation](/docs/cli/mcp.md) for the full list of available tools and setup options for other clients. ## Try It Yourself[​](#try-it-yourself "Direct link to Try It Yourself") 1. [Install ApiGear](/docs/cli/intro.md#installation) 2. [Register the MCP server](/docs/cli/mcp.md#setup) with your AI assistant 3. Ask your assistant to design an API from a plain English description — and generate an SDK No spec syntax to memorize. No commands to look up. Just describe your API and let the assistant do the rest. **Tags:** * [mcp](/blog/tags/mcp.md) * [ai](/blog/tags/ai.md) * [sdk](/blog/tags/sdk.md) * [code-generation](/blog/tags/code-generation.md) --- # API surface for objects February 27, 2023 · 3 min read ![Jürgen Ryannel](https://avatars.githubusercontent.com/u/636357?v=4) Jürgen Ryannel Lead Programmer An API surface is the public interface of an object which is exposed to users of the object. The API surface is defined by an IDL and defines the contract between the user and the implementer of an object. The API surface for an object consists of the following parts: * Properties: Properties are the state of an object. They can be read and written and notify users about changes. Properties can be of any type. * Operations: Operations are functions that can be called on an object. They can be synchronous or asynchronous. Operations can receive parameters and return a value. * Signals: Signals are events that can be emitted by an object. They can be received by users of the object. ``` interface name { `prop-name` : type // property `operation-name` ( params ): `type` // operation signal `signal-name` ( params ) // signal } ``` For example a heating system could have the following API surface: ``` interface HeatingSystem { temperature: float setTemperature(float temperature) signal maxTemperatureReached() } ``` As users of an API you can use the API surface to understand how to use an object. You can also use the API surface to understand how to implement an object. The API surface is the contract between the user and the implementer of an object. ## Properties[​](#properties "Direct link to Properties") Properties are the state of an object. They can be read and written and notify users about changes. Properties can be of any type. ``` interface HeatingSystem { temperature: float } ``` In the example above the `temperature` property is of type `float`. The type can be any type that is supported by the IDL. The type can be a primitive type or a complex type. A primitive type can be one of the following: * `int` : integer value * `float` : floating point value * `string` : string value * `bool` : boolean value Types can also be arrays. To define arrays of a type just append the `[]` to the type (for example `int[]`). ## Operations[​](#operations "Direct link to Operations") Operations are functions that can be called on an object. They can be synchronous or asynchronous. Operations can receive parameters and return a value. ``` interface HeatingSystem { setTemperature(float temperature) } ``` In the example above the `setTemperature` operation receives a parameter of type `float`. The operation does not return a value. ## Signals[​](#signals "Direct link to Signals") Signals are events that can be emitted by an object. They can be received by users of the object. ``` interface HeatingSystem { signal maxTemperatureReached() } ``` In the example above the `maxTemperatureReached` signal does not receive any parameters. ## Structures[​](#structures "Direct link to Structures") Different than objects which can be interacted with, structures are just data structures that can be used to send complex information between objects. A structure is defined as follows: ``` struct Struct1 {} ``` A struct can have fields of any type. ## Enumerations[​](#enumerations "Direct link to Enumerations") An enumeration is a set of named values. An enumeration is defined as follows: ``` enum Enum1 { value1 value2 } ``` An enumeration can be used as a type for properties, operations and signals. ## Modules[​](#modules "Direct link to Modules") A module is a collection of objects, structures and enumerations. A module is defined as follows: ``` module name version interface Name {} ``` The version of a module is a string that follows the [semver](https://semver.org/) format. The version of a module is optional. If no version is specified the version is `0.0.0`. # Working with the API surface When designing an API surface it is important to keep the following in mind: **Tags:** * [api](/blog/tags/api.md) * [idl](/blog/tags/idl.md) --- ### 2026[​](#2026 "Direct link to 2026") * [March 2](/blog/ai-meets-api-design-mcp.md) [ - ](/blog/ai-meets-api-design-mcp.md) [AI Meets API Design: MCP-Powered SDK Generation](/blog/ai-meets-api-design-mcp.md) * [April 30](/blog/unreal-android-jni-bridge.md) [ - ](/blog/unreal-android-jni-bridge.md) [ApiGear on Android: Bridging Unreal Engine and Java with Generated JNI](/blog/unreal-android-jni-bridge.md) --- # Authors * ![Jürgen Ryannel](https://avatars.githubusercontent.com/u/636357?v=4) ## Jürgen Ryannel 1 Lead Programmer * ![Wolfgang Bremer](https://avatars.githubusercontent.com/u/11640828?v=4) ## Wolfgang Bremer 2 Co-Founder [](https://github.com/w4bremer "GitHub") --- ## A[​](#A "Direct link to A") * [ai1](/blog/tags/ai.md) * [android1](/blog/tags/android.md) * [api1](/blog/tags/api.md) *** --- ## [AI Meets API Design: MCP-Powered SDK Generation](/blog/ai-meets-api-design-mcp.md) March 2, 2026 · 3 min read ![Wolfgang Bremer](https://avatars.githubusercontent.com/u/11640828?v=4) Wolfgang Bremer Co-Founder [](https://github.com/w4bremer "GitHub") What if you could describe an API in plain English and your AI assistant would design the interface, validate it, and generate a production-ready SDK — all without leaving your editor? That's exactly what happens when you connect ApiGear to your AI coding assistant via MCP. No spec syntax to learn, no CLI commands to memorize. You describe what you need, and your assistant handles the rest — from API design to generated code. **Tags:** * [mcp](/blog/tags/mcp.md) * [ai](/blog/tags/ai.md) * [sdk](/blog/tags/sdk.md) * [code-generation](/blog/tags/code-generation.md) [**Read more**](/blog/ai-meets-api-design-mcp.md) --- ## [ApiGear on Android: Bridging Unreal Engine and Java with Generated JNI](/blog/unreal-android-jni-bridge.md) April 30, 2026 · 11 min read ![Wolfgang Bremer](https://avatars.githubusercontent.com/u/11640828?v=4) Wolfgang Bremer Co-Founder [](https://github.com/w4bremer "GitHub") You already have an Android app. It owns the data — vehicle telemetry, sensor state, transactional records — and exposes it through a Java or Kotlin service layer. Now product wants a richer surface on top: a 3D instrument cluster, an operator dashboard, a kiosk experience that needs more than the standard Android UI toolkit gives you. The natural answer is to add an Unreal Engine app as a rendering tier — a second process that draws the HMI and reaches back into your existing Java service for the data it visualizes. That's a cross-process Android problem with a Java service host on one end and a C++ engine consumer on the other. Bind a `Service`, write a `Messenger` handler, define a `Parcelable` for every payload, hand-roll a JNI surface so the engine's C++ can call into the Java client, then keep the method signatures in sync forever. ApiGear's new [Java template](/template-java/docs/intro.md) ships the Android counterpart to the Unreal Engine template's new [JNI feature](/template-unreal/docs/features/jni.md), which generates both a JNI client (for consuming an Android service) and a JNI adapter (for exposing one). Define your interface once, generate both sides, and the Messenger plumbing, parcels, and JNI bindings appear together. This post walks through the realistic case — *Android app hosts the service, UE app consumes it* — and shows what comes out of the generator. **Tags:** * [unreal](/blog/tags/unreal.md) * [android](/blog/tags/android.md) * [java](/blog/tags/java.md) * [jni](/blog/tags/jni.md) * [ipc](/blog/tags/ipc.md) [**Read more**](/blog/unreal-android-jni-bridge.md) --- ## [API surface for objects](/blog/api-surface.md) February 27, 2023 · 3 min read ![Jürgen Ryannel](https://avatars.githubusercontent.com/u/636357?v=4) Jürgen Ryannel Lead Programmer An API surface is the public interface of an object which is exposed to users of the object. The API surface is defined by an IDL and defines the contract between the user and the implementer of an object. **Tags:** * [api](/blog/tags/api.md) * [idl](/blog/tags/idl.md) [**Read more**](/blog/api-surface.md) --- ## [AI Meets API Design: MCP-Powered SDK Generation](/blog/ai-meets-api-design-mcp.md) March 2, 2026 · 3 min read ![Wolfgang Bremer](https://avatars.githubusercontent.com/u/11640828?v=4) Wolfgang Bremer Co-Founder [](https://github.com/w4bremer "GitHub") What if you could describe an API in plain English and your AI assistant would design the interface, validate it, and generate a production-ready SDK — all without leaving your editor? That's exactly what happens when you connect ApiGear to your AI coding assistant via MCP. No spec syntax to learn, no CLI commands to memorize. You describe what you need, and your assistant handles the rest — from API design to generated code. **Tags:** * [mcp](/blog/tags/mcp.md) * [ai](/blog/tags/ai.md) * [sdk](/blog/tags/sdk.md) * [code-generation](/blog/tags/code-generation.md) [**Read more**](/blog/ai-meets-api-design-mcp.md) --- ## [API surface for objects](/blog/api-surface.md) February 27, 2023 · 3 min read ![Jürgen Ryannel](https://avatars.githubusercontent.com/u/636357?v=4) Jürgen Ryannel Lead Programmer An API surface is the public interface of an object which is exposed to users of the object. The API surface is defined by an IDL and defines the contract between the user and the implementer of an object. **Tags:** * [api](/blog/tags/api.md) * [idl](/blog/tags/idl.md) [**Read more**](/blog/api-surface.md) --- ## [ApiGear on Android: Bridging Unreal Engine and Java with Generated JNI](/blog/unreal-android-jni-bridge.md) April 30, 2026 · 11 min read ![Wolfgang Bremer](https://avatars.githubusercontent.com/u/11640828?v=4) Wolfgang Bremer Co-Founder [](https://github.com/w4bremer "GitHub") You already have an Android app. It owns the data — vehicle telemetry, sensor state, transactional records — and exposes it through a Java or Kotlin service layer. Now product wants a richer surface on top: a 3D instrument cluster, an operator dashboard, a kiosk experience that needs more than the standard Android UI toolkit gives you. The natural answer is to add an Unreal Engine app as a rendering tier — a second process that draws the HMI and reaches back into your existing Java service for the data it visualizes. That's a cross-process Android problem with a Java service host on one end and a C++ engine consumer on the other. Bind a `Service`, write a `Messenger` handler, define a `Parcelable` for every payload, hand-roll a JNI surface so the engine's C++ can call into the Java client, then keep the method signatures in sync forever. ApiGear's new [Java template](/template-java/docs/intro.md) ships the Android counterpart to the Unreal Engine template's new [JNI feature](/template-unreal/docs/features/jni.md), which generates both a JNI client (for consuming an Android service) and a JNI adapter (for exposing one). Define your interface once, generate both sides, and the Messenger plumbing, parcels, and JNI bindings appear together. This post walks through the realistic case — *Android app hosts the service, UE app consumes it* — and shows what comes out of the generator. **Tags:** * [unreal](/blog/tags/unreal.md) * [android](/blog/tags/android.md) * [java](/blog/tags/java.md) * [jni](/blog/tags/jni.md) * [ipc](/blog/tags/ipc.md) [**Read more**](/blog/unreal-android-jni-bridge.md) --- ## [ApiGear on Android: Bridging Unreal Engine and Java with Generated JNI](/blog/unreal-android-jni-bridge.md) April 30, 2026 · 11 min read ![Wolfgang Bremer](https://avatars.githubusercontent.com/u/11640828?v=4) Wolfgang Bremer Co-Founder [](https://github.com/w4bremer "GitHub") You already have an Android app. It owns the data — vehicle telemetry, sensor state, transactional records — and exposes it through a Java or Kotlin service layer. Now product wants a richer surface on top: a 3D instrument cluster, an operator dashboard, a kiosk experience that needs more than the standard Android UI toolkit gives you. The natural answer is to add an Unreal Engine app as a rendering tier — a second process that draws the HMI and reaches back into your existing Java service for the data it visualizes. That's a cross-process Android problem with a Java service host on one end and a C++ engine consumer on the other. Bind a `Service`, write a `Messenger` handler, define a `Parcelable` for every payload, hand-roll a JNI surface so the engine's C++ can call into the Java client, then keep the method signatures in sync forever. ApiGear's new [Java template](/template-java/docs/intro.md) ships the Android counterpart to the Unreal Engine template's new [JNI feature](/template-unreal/docs/features/jni.md), which generates both a JNI client (for consuming an Android service) and a JNI adapter (for exposing one). Define your interface once, generate both sides, and the Messenger plumbing, parcels, and JNI bindings appear together. This post walks through the realistic case — *Android app hosts the service, UE app consumes it* — and shows what comes out of the generator. **Tags:** * [unreal](/blog/tags/unreal.md) * [android](/blog/tags/android.md) * [java](/blog/tags/java.md) * [jni](/blog/tags/jni.md) * [ipc](/blog/tags/ipc.md) [**Read more**](/blog/unreal-android-jni-bridge.md) --- ## [ApiGear on Android: Bridging Unreal Engine and Java with Generated JNI](/blog/unreal-android-jni-bridge.md) April 30, 2026 · 11 min read ![Wolfgang Bremer](https://avatars.githubusercontent.com/u/11640828?v=4) Wolfgang Bremer Co-Founder [](https://github.com/w4bremer "GitHub") You already have an Android app. It owns the data — vehicle telemetry, sensor state, transactional records — and exposes it through a Java or Kotlin service layer. Now product wants a richer surface on top: a 3D instrument cluster, an operator dashboard, a kiosk experience that needs more than the standard Android UI toolkit gives you. The natural answer is to add an Unreal Engine app as a rendering tier — a second process that draws the HMI and reaches back into your existing Java service for the data it visualizes. That's a cross-process Android problem with a Java service host on one end and a C++ engine consumer on the other. Bind a `Service`, write a `Messenger` handler, define a `Parcelable` for every payload, hand-roll a JNI surface so the engine's C++ can call into the Java client, then keep the method signatures in sync forever. ApiGear's new [Java template](/template-java/docs/intro.md) ships the Android counterpart to the Unreal Engine template's new [JNI feature](/template-unreal/docs/features/jni.md), which generates both a JNI client (for consuming an Android service) and a JNI adapter (for exposing one). Define your interface once, generate both sides, and the Messenger plumbing, parcels, and JNI bindings appear together. This post walks through the realistic case — *Android app hosts the service, UE app consumes it* — and shows what comes out of the generator. **Tags:** * [unreal](/blog/tags/unreal.md) * [android](/blog/tags/android.md) * [java](/blog/tags/java.md) * [jni](/blog/tags/jni.md) * [ipc](/blog/tags/ipc.md) [**Read more**](/blog/unreal-android-jni-bridge.md) --- ## [AI Meets API Design: MCP-Powered SDK Generation](/blog/ai-meets-api-design-mcp.md) March 2, 2026 · 3 min read ![Wolfgang Bremer](https://avatars.githubusercontent.com/u/11640828?v=4) Wolfgang Bremer Co-Founder [](https://github.com/w4bremer "GitHub") What if you could describe an API in plain English and your AI assistant would design the interface, validate it, and generate a production-ready SDK — all without leaving your editor? That's exactly what happens when you connect ApiGear to your AI coding assistant via MCP. No spec syntax to learn, no CLI commands to memorize. You describe what you need, and your assistant handles the rest — from API design to generated code. **Tags:** * [mcp](/blog/tags/mcp.md) * [ai](/blog/tags/ai.md) * [sdk](/blog/tags/sdk.md) * [code-generation](/blog/tags/code-generation.md) [**Read more**](/blog/ai-meets-api-design-mcp.md) --- ## [AI Meets API Design: MCP-Powered SDK Generation](/blog/ai-meets-api-design-mcp.md) March 2, 2026 · 3 min read ![Wolfgang Bremer](https://avatars.githubusercontent.com/u/11640828?v=4) Wolfgang Bremer Co-Founder [](https://github.com/w4bremer "GitHub") What if you could describe an API in plain English and your AI assistant would design the interface, validate it, and generate a production-ready SDK — all without leaving your editor? That's exactly what happens when you connect ApiGear to your AI coding assistant via MCP. No spec syntax to learn, no CLI commands to memorize. You describe what you need, and your assistant handles the rest — from API design to generated code. **Tags:** * [mcp](/blog/tags/mcp.md) * [ai](/blog/tags/ai.md) * [sdk](/blog/tags/sdk.md) * [code-generation](/blog/tags/code-generation.md) [**Read more**](/blog/ai-meets-api-design-mcp.md) --- ## [ApiGear on Android: Bridging Unreal Engine and Java with Generated JNI](/blog/unreal-android-jni-bridge.md) April 30, 2026 · 11 min read ![Wolfgang Bremer](https://avatars.githubusercontent.com/u/11640828?v=4) Wolfgang Bremer Co-Founder [](https://github.com/w4bremer "GitHub") You already have an Android app. It owns the data — vehicle telemetry, sensor state, transactional records — and exposes it through a Java or Kotlin service layer. Now product wants a richer surface on top: a 3D instrument cluster, an operator dashboard, a kiosk experience that needs more than the standard Android UI toolkit gives you. The natural answer is to add an Unreal Engine app as a rendering tier — a second process that draws the HMI and reaches back into your existing Java service for the data it visualizes. That's a cross-process Android problem with a Java service host on one end and a C++ engine consumer on the other. Bind a `Service`, write a `Messenger` handler, define a `Parcelable` for every payload, hand-roll a JNI surface so the engine's C++ can call into the Java client, then keep the method signatures in sync forever. ApiGear's new [Java template](/template-java/docs/intro.md) ships the Android counterpart to the Unreal Engine template's new [JNI feature](/template-unreal/docs/features/jni.md), which generates both a JNI client (for consuming an Android service) and a JNI adapter (for exposing one). Define your interface once, generate both sides, and the Messenger plumbing, parcels, and JNI bindings appear together. This post walks through the realistic case — *Android app hosts the service, UE app consumes it* — and shows what comes out of the generator. **Tags:** * [unreal](/blog/tags/unreal.md) * [android](/blog/tags/android.md) * [java](/blog/tags/java.md) * [jni](/blog/tags/jni.md) * [ipc](/blog/tags/ipc.md) [**Read more**](/blog/unreal-android-jni-bridge.md) --- # ApiGear on Android: Bridging Unreal Engine and Java with Generated JNI April 30, 2026 · 11 min read ![Wolfgang Bremer](https://avatars.githubusercontent.com/u/11640828?v=4) Wolfgang Bremer Co-Founder [](https://github.com/w4bremer "GitHub") You already have an Android app. It owns the data — vehicle telemetry, sensor state, transactional records — and exposes it through a Java or Kotlin service layer. Now product wants a richer surface on top: a 3D instrument cluster, an operator dashboard, a kiosk experience that needs more than the standard Android UI toolkit gives you. The natural answer is to add an Unreal Engine app as a rendering tier — a second process that draws the HMI and reaches back into your existing Java service for the data it visualizes. That's a cross-process Android problem with a Java service host on one end and a C++ engine consumer on the other. Bind a `Service`, write a `Messenger` handler, define a `Parcelable` for every payload, hand-roll a JNI surface so the engine's C++ can call into the Java client, then keep the method signatures in sync forever. ApiGear's new [Java template](/template-java/docs/intro.md) ships the Android counterpart to the Unreal Engine template's new [JNI feature](/template-unreal/docs/features/jni.md), which generates both a JNI client (for consuming an Android service) and a JNI adapter (for exposing one). Define your interface once, generate both sides, and the Messenger plumbing, parcels, and JNI bindings appear together. This post walks through the realistic case — *Android app hosts the service, UE app consumes it* — and shows what comes out of the generator. ## The architecture[​](#the-architecture "Direct link to The architecture") The deployment we care about pairs an existing Android codebase with a UE-rendered front end: * **Your Android app** is the system of record. Its existing repositories, daemons, and domain logic own the data. The Java template generates the Android `Service` that exposes that logic over `Messenger` IPC. * **Your UE app** is the rendering tier. The Unreal Engine template generates the C++ JNI Client that calls into a generated Java client, which in turn binds to the Android service in the other process. The C++ side never sees `Messenger`; it sees a typed C++ subsystem. Figure: Service/client architecture: the Android app hosts the data, the Java template generates the Messenger service, the Unreal Engine template generates the JNI client the UE C++ code talks to. The Java template generates the `Service`, the `Messenger` handler, the `Parcelable` payloads, and a Java client any other process can drop in (see the [`android` feature reference](/template-java/docs/features/android.md) for the full output surface). The Unreal Engine template generates the C++ JNI Client that wraps the generated Java client — UE C++ code calls typed methods, JNI dispatches them through the Java client over the Messenger boundary to the host service (see the [`jni` feature reference](/template-unreal/docs/features/jni.md)). You write the backend (in Java or Kotlin, plugging into your existing data layer) and the C++ rendering code that consumes results. Everything between is generated. ## The IDL[​](#the-idl "Direct link to The IDL") The same `HelloWorld` example used throughout the [ApiGear quick start](/docs/guide/quick-start.md) is enough to show the shape end-to-end. One property, one signal, one operation — and a custom struct and enum so the cross-language type story shows up: helloworld.module.yaml ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` The IDL is direction-agnostic — the same module describes the API regardless of which side hosts it. The solution document is where the deployment shape shows up. We need three targets, because the Java template generates code for *both* sides of the Messenger boundary; host and consumer pick different classes from the same output: helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: # The existing Android app: hosts the service. - name: android_service inputs: - helloworld.module.yaml output: ../AndroidApp/app/src/main/java template: apigear-io/template-java@v0.1.0 force: true features: - api - android - stubs # The UE app: needs the Java-side Messenger client under android/. - name: ue_java inputs: - helloworld.module.yaml output: ../UEProject/android template: apigear-io/template-java@v0.1.0 force: true features: - api - android - jnibridge # The UE app: C++ JNI Client under Plugins/. - name: ue_cpp inputs: - helloworld.module.yaml output: ../UEProject/Plugins template: apigear-io/template-unreal@v4.0.0 force: true features: - api - jni - stubs ``` The Android app target picks up [`stubs`](/template-java/docs/features/stubs.md) so you also get a starting backend implementation; the UE-side Java target enables [`jnibridge`](/template-java/docs/features/jnibridge.md) so the generated C++ JNI client has a matching Java-side bridge to call into. Run `apigear generate solution helloworld.solution.yaml` and three output trees drop into place from one module file. ## What gets generated[​](#what-gets-generated "Direct link to What gets generated") The excerpts below come from a fresh run against the module above. Paths are relative to your output trees so you can find them in your own project. ### On the Android service host (Java template)[​](#on-the-android-service-host-java-template "Direct link to On the Android service host (Java template)") The Java API surface is a plain interface — getters, setters, methods, event listeners. From `ioWorld/ioWorld_api/src/main/java/ioWorld/ioWorld_api/IHello.java`: IHello.java ``` public interface IHello { // properties void setLast(Message last); Message getLast(); void fireLastChanged(Message newValue); // methods int say(Message msg, When when); CompletableFuture sayAsync(Message msg, When when); public void fireJustSaid(Message msg); void addEventListener(IHelloEventListener listener); // ... } ``` This is the seam between generated plumbing and your existing code. With `stubs` enabled, the template emits a starter implementation at `ioWorld/ioWorld_impl/src/main/java/ioWorld/ioWorld_impl/HelloService.java` that you replace with calls into your real data layer: HelloService.java ``` public class HelloService extends AbstractHello { private Message m_last = new Message(); @Override public void setLast(Message last) { if (m_last == null || !m_last.equals(last)) { m_last = last; // <- replace with your repository call onLastChanged(m_last); // notifies bound clients } } @Override public Message getLast() { return m_last; // <- read from your existing data source } @Override public int say(Message msg, When when) { // <- delegate to your existing service / domain logic return 0; } } ``` No C++ on this side. No JNI. The `onLastChanged` / `fireJustSaid` calls fan out through the generated event listener machinery to whoever is bound — including the UE app's client. The Messenger service that wraps that backend is generated alongside it. From `ioWorld/ioWorld_android_service/src/main/java/ioWorld/ioWorld_android_service/HelloServiceAdapter.java`: HelloServiceAdapter.java ``` public class HelloServiceAdapter extends Service { private Messenger mMessenger; private static IHello mBackendService; @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } class IncomingHandler extends Handler implements IHelloEventListener { @Override public void handleMessage(Message msg) { HelloMessageType msgType = HelloMessageType.fromInteger(msg.what); switch (msgType) { case REGISTER_CLIENT: /* track replyTo, send INIT snapshot */ break; case PROP_Last: mBackendService.setLast(/* parcel */); break; case RPC_SayReq: /* invoke backend.say, post RPC_SayResp */ break; // ... } } } } ``` The handler decodes a generated message-type enum, unpacks parcels, dispatches to your backend, and pushes property changes and signals back out to every bound client. The IPC schema is shared between both sides as a single enum — the same `HelloMessageType.java` is consumed by the service and by the UE-side client: HelloMessageType.java ``` public enum HelloMessageType { REGISTER_CLIENT(0), UNREGISTER_CLIENT(1), INIT(2), PROP_Last(3), SET_Last(4), SIG_JustSaid(5), RPC_SayReq(6), RPC_SayResp(7), HelloMessageType_UNKNOWN(Integer.MAX_VALUE); // ... } ``` The Android `Service` declarations are pre-wired in the manifest at `ioWorld_android_service/src/main/AndroidManifest.xml`: AndroidManifest.xml ``` ``` One service per interface, exported, ready to bind. You don't author this file — the template emits it. ### In the Unreal Engine client (Unreal Engine template)[​](#in-the-unreal-engine-client-unreal-engine-template "Direct link to In the Unreal Engine client (Unreal Engine template)") The UE plugin's JNI module wraps the generated Java client (`HelloJniClient`) so your C++ code never touches `Messenger` directly. From `IoWorld/Source/IoWorldJni/Private/Generated/Jni/IoWorldHelloJniClient.cpp`: IoWorldHelloJniClient.cpp ``` void UIoWorldHelloJniClient::SetLast(const FIoWorldMessage& InLast) { if (GetLast() == InLast) { return; } // de-dup writes #if PLATFORM_ANDROID && USE_ANDROID_JNI if (JNIEnv* Env = FAndroidApplication::GetJavaEnv()) { auto Cache = UIoWorldHelloJniClientCache::Get(); jmethodID MethodID = Cache->LastSetterId; // resolved once at startup jobject jlocal_Last = IoWorldDataJavaConverter::makeJavaMessage(Env, InLast); FJavaWrapper::CallVoidMethod(Env, m_javaJniClientInstance, MethodID, jlocal_Last); Env->DeleteLocalRef(jlocal_Last); } #endif } ``` The same `say` operation shows up as `say(Message, When)` in Java, as `nativeOnSayResult` callbacks coming back over JNI, and as `Say(const FIoWorldMessage&, EIoWorldWhen)` in UE C++ — generated together, kept in sync forever. The `makeJavaMessage` and matching `makeJavaWhen` helpers — the converters that move your custom struct and enum across the JNI boundary — are generated next door in `IoWorldDataJavaConverter.cpp`. ## Wiring it up[​](#wiring-it-up "Direct link to Wiring it up") Three things you still own, plus one note for Blueprint use: **The backend implementation.** The generated `HelloService` is a stub in your Android app's source tree. Replace it with your real implementation, wrapping whatever data layer you have today — repository, domain service, daemon. The generated code never touches that layer; it just calls your `IHello` implementation. **Binding from UE to the host service.** The generated UE C++ subsystem (`UIoWorldHelloJniClient`) exposes a `_bindToService(servicePackage, connectionId)` call that takes the *Android app's* package name; it can be driven from blueprints. See the [JNI feature docs](/template-unreal/docs/features/jni.md) for the package-name coordination details — the Android manifest entries the UE side needs (service declarations, queries for cross-process binding, and Java source inclusion) are emitted into an Unreal Plugin Language file (`IoWorld_JNI_UPL.xml`) and applied automatically by the build. **Reconnect logic, if you want any.** The generated client does not auto-reconnect when the host service dies. If your deployment cares — most embedded ones will — handle it on the UE side, around the `_bindToService` call. **Blueprint use.** The generated client is `BlueprintType`, so cluster artists and HMI designers can consume the cross-process data directly from Blueprints — operations, property accessors, and signal subscriptions all surface through the same Blueprint-exposure pattern ApiGear's OLink and MsgBus clients already use, with no JNI-specific extra wiring on the BP side. Subclassing the client itself is disabled (`NotBlueprintable`); you consume the instance, you don't extend it. A few constraints worth flagging up front: * Minimum Android SDK is 33 on both apps. * Each interface in your module becomes a separate Android service. That's by design (each is independently bindable) but counts against your manifest budget. * Custom struct and enum types translate automatically; `extern` types still need a hand-written converter (`fillYourExternName`, `makeJavaYourExternName` skeletons are generated for you to fill in). ## When this is the right tool[​](#when-this-is-the-right-tool "Direct link to When this is the right tool") The pairing earns its keep when the Android side already exists and the UE side is the new surface: * **Automotive HMI.** Vehicle data — vehicle properties surfaced by CarService and VHAL, plus app-layer telematics, media, and navigation — already lives in Java/Kotlin services on the head unit. The UE app renders the cluster or center stack as a client, binding to those services for live data. * **IoT gateway.** Device daemons in Java or Kotlin own sensor state and protocol bridges. The UE app renders an operator dashboard as a client, subscribing to signals and reading current values. * **Kiosk and embedded HMI.** The kiosk's domain logic — payments, inventory, session state — lives in an Android service. The UE app renders the customer-facing surface and binds to read state and invoke operations. The same template pair *also* supports the inverse direction — UE hosts the service, Java/Kotlin clients consume it. That's the case Epic documents in [Setting up Android Single Instance Service in Unreal Engine](https://dev.epicgames.com/documentation/unreal-engine/setting-up-android-single-instance-service-in-unreal-engine). On the C++ side you instantiate the generated JNI *adapter* instead of the JNI client; the Java template's service classes flip from being consumed by the UE Java client to hosting the UE-side Java service. (`jnibridge` stays on the Java target either way — it's what generates the Java side of the JNI bridge, not a direction flag.) Reach for it when the C++ side genuinely owns long-lived state — typically when the UE app is the only thing on the device and other apps are diagnostic helpers around it. It's the wrong tool for single-process apps. If your UE code and Java code share a process, you don't need IPC and you don't need this generator pair — use the local implementation directly. ## Try it[​](#try-it "Direct link to Try it") * [Java template documentation](/template-java/docs/intro.md) — start here for the Android side. * [`android` feature reference](/template-java/docs/features/android.md) — what the `Service`, `Messenger`, and `Parcelable` generation actually produces. * [`jnibridge` feature reference](/template-java/docs/features/jnibridge.md) — the Java-side half of the cross-language bridge. * [Unreal Engine template documentation](/template-unreal/docs/intro.md) — the JNI feature lives alongside the existing OLink, MsgBus, and Monitor features. * [`jni` feature reference](/template-unreal/docs/features/jni.md) — the C++ JNI Client and Adapter, plus the manifest plumbing. * [ApiGear quick start](/docs/guide/quick-start.md) — install the CLI and generate your first module. Define the interface once. Generate both sides. Stop hand-syncing JNI signatures. **Tags:** * [unreal](/blog/tags/unreal.md) * [android](/blog/tags/android.md) * [java](/blog/tags/java.md) * [jni](/blog/tags/jni.md) * [ipc](/blog/tags/ipc.md) --- # Features This guide explains how to use the generated code, what are the available features and their benefits. info A feature is a part of the template that generates a specific aspect of the code. For example, the `api` feature generates the core API interfaces and the `stubs` feature generates a stub implementation for the API. ## Get started[​](#get-started "Direct link to Get started") This template generates code for pure c++ projects. In order to successfully compile and use the code, you need to have a working c++ compiler toolchain installed. note Basic c++ knowledge is necessary. ### Code generation[​](#code-generation "Direct link to Code generation") Follow the documentation for the [code generation](/docs/guide/quick-start.md) in general and [CLI](/docs/cli/generate.md) or the [Studio](/docs/studio/intro.md) tools. Or try first the [quick start guide](/template-cpp14/docs/quickstart.md) which shows how to prepare api and generate code out of it. tip For questions regarding the template please go to our [discussions page](https://github.com/orgs/apigear-io/discussions). For feature requests or bug reports please use our [issue tracker](https://github.com/apigear-io/template-cpp14/issues). ### Example API[​](#example-api "Direct link to Example API") The following code snippet contains the *API* definition which is used throughout this guide to demonstrate the generated code and its usage. Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` ## Features[​](#features-1 "Direct link to Features") ### Core Features[​](#core-features "Direct link to Core Features") Features generate a view model for the *API* definition. This can be used to implement a working *C++* service and directly use it in your project. * [api](/template-cpp14/docs/features/api.md) - generates compilable base pure interfaces for your *API*, as also a basic implementation for data types and subscriber/publisher as abstract classes that describe the notification system for users of your interfaces. * [stubs](/template-cpp14/docs/features/stubs.md) - adds a basic stub implementation for the *API*, you'll get classes that can actually be instantiated with a ready to use publishers, this is a good starting point for your implementation. * [conan](/template-cpp14/docs/features/conan.md) - build and packaging support for your project. ### Extended Features[​](#extended-features "Direct link to Extended Features") Features can be used in combination with *API* and add more functionality on top, like simulation support (see [olink](/template-cpp14/docs/features/olink.md#simulation)) * [olink](/template-cpp14/docs/features/olink.md) - provides a client and server adapters for each interface, that can be connected to any of the other technology templates with support for [ObjectLink](/docs/protocols/objectlink/intro.md). Use this feature to connect with ApiGear simulation tools. * examples\_olink - examples of generated code for the olink feature. Contains: * `olinkserver` shows use of your interfaces as an olink services. * `olinkclient` shows use of your interfaces as an olink clients. * [monitor](/template-cpp14/docs/features/monitor.md) - generates a middle-ware layer which logs all API events to the [CLI](/docs/cli/intro.md) or the [Studio](/docs/studio/intro.md) * [MQTT](/template-cpp14/docs/features/mqtt.md) *experimental* - provides minimal working adapters for MQTT client and service side for each interfaces. Check also MQTT in other technology templates that supports it. * examples\_mqtt - examples of generated code for the olink feature. Contains: * `mqttserver` shows use of your interfaces with mqtt adapted for your services. * `mqttclient` shows use of your interfaces as a mqtt adapted for your services users. * examples of generated code for basic features: * `app` simple example with stubs. * `appthreadsafe` simple example with stubs wrapped with thread safe decorator. See [thread safe decorator from core features](/template-cpp14/docs/features/stubs.md#core) ![Overview of features for user application, including receiving data from network: Bottom floor shows possible inputs for your API, you can either obtain data from the network with OLink or MQTT or use local (thread safe if necessary) implementation.](/img/cpp14/features/featuresApp.png) Figure: Overview of features for user application, including receiving data from network: Bottom floor shows possible inputs for your API, you can either obtain data from the network with OLink or MQTT or use local (thread safe if necessary) implementation. note Theoretically you can use the Thread Safe Decorator with other features that implements API: OLinkClient and MQTT Client, but they, already provide thread safety on at least the same level as the Thread Safe Decorator. ![Overview of features for user application, including publishing data through network: Topmost floor shows your options for using your local implementation (bottom floor): you can use it in your local app and/or use method of sharing the data with clients in the network. Consider then using thread safe version of your implementation.](/img/cpp14/features/featuresServer.png) Figure: Overview of features for user application, including publishing data through network: Topmost floor shows your options for using your local implementation (bottom floor): you can use it in your local app and/or use method of sharing the data with clients in the network. Consider then using thread safe version of your implementation. There are also an *internal* features: * `apigear`, which is generated for the *extended* features and is explained with them. * `core`, which is generated for the stubs and the *extended* features. For the explanation see [core documentation](/template-cpp14/docs/features/stubs.md#core) Each feature can be selected using the solution file or via command line tool. note \_Features are case sensitive, make sure to always **use lower-case.** \_ tip The *meta* feature `all` enables all specified features of the template. If you want to see the full extent of the generated code `all` is easiest solution. Please note, `all` is part of the code generator and not explicitly used within templates. ## Folder structure[​](#folder-structure "Direct link to Folder structure") This graph shows the full folder structure which is generated for `all` features enabled, but skips the [conan](/template-cpp14/docs/features/conan.md) files. Generated features are encapsulated in separate folders for each module or for the common features like `examples` and the internal helper feature `apigear`, a level above, in the `generation target` level, here `cpp_hello_world`. For more details visit the documentation for each feature. ``` 📂hello-world ┣ 📂apigear ┃ ┣ 📜helloworld.solution.yaml ┃ ┗ 📜helloworld.module.yaml ┣ 📂cpp_hello_world ┃ ┣ 📂apigear ┃ ┣ 📂examples ┃ ┣ 📂modules ┃ ┃ ┗ 📂io_world ┃ ┃ ┃ ┣ 📂conan ┃ ┃ ┃ ┣ 📂generated ┃ ┃ ┃ ┃ ┣ 📂api ┃ ┃ ┃ ┃ ┣ 📂core ┃ ┃ ┃ ┃ ┣ 📂monitor ┃ ┃ ┃ ┃ ┣ 📂mqtt ┃ ┃ ┃ ┃ ┗ 📂olink ┃ ┃ ┃ ┣ 📂implementation ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┗ 📜Io_worldConfig.cmake.in ┃ ┗ 📜CMakeLists.txt ``` --- # API Feature The feature `api` is the bare minimum for code generation. The feature will generate: * core behavioral API for the defined *interfaces* * interfaces for [publisher](/template-cpp14/docs/features/api.md#publisher) and [subscriber](/template-cpp14/docs/features/api.md#subscriber) tailored for each interface * Enumerations for the defined *enums* * Data structured with a core implementation for the defined *structs* note Check out the [stubs](/template-cpp14/docs/features/stubs.md) feature which provides fully functional implementation for the publisher. ### Files overview per module[​](#files-overview-per-module "Direct link to Files overview per module") Using the example API definition Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure will be generated inside the target folder. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┣ 📂cpp_hello_world ┃ ┣ 📂apigear ┃ ┣ 📂examples ┃ ┣ 📂modules ┃ ┃ ┗ 📂io_world ┃ ┃ ┃ ┣ 📂generated ┃ ┃ ┃ ┃ ┣ 📂api ┃ ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┃ ┣ 📜common.h ┃ ┃ ┃ ┃ ┃ ┣ 📜datastructs.api.cpp ┃ ┃ ┃ ┃ ┃ ┣ 📜datastructs.api.h ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.api.h ┃ ┃ ┃ ┃ ┃ ┗ 📜io_world.h .. . ``` ### Enums and Structures[​](#enums-and-structures "Direct link to Enums and Structures") Files `datastructs.api.h` and `datastructs.api.cpp` contain core data structures and enumerations used in your module. #### Enums[​](#enums "Direct link to Enums") Inside the `datastructs.api.h` you will find all the enums defined in your API. For each enum there is a `enum class` generated with all the values defined. For convenience of use each enum has a `toEnum` function, which converts given `uint8_t` to enum value. #### Structs[​](#structs "Direct link to Structs") All structs defined in your API are generated in `datastructs.api.h` and `datastructs.api.cpp` files. They are used to communicate data between your API and its users. For convenience of use each structure has: * equality comparison operators * empty constructor and a constructor with parameters for all its fields. ### Interfaces[​](#interfaces "Direct link to Interfaces") For each interface inside an AP module there is separate file generated, like the `📜hello.api.h`. The interface class contains pure virtual functions as well as pure virtual subscriber and publisher classes. The interface has: * pure virtual `getters` and a `setters` for each property ``` virtual void setLast(const Message& last) = 0; virtual const Message& getLast() const = 0; ``` * pure virtual functions for each operation ``` virtual int say(const Message& msg, When::WhenEnum when) = 0; ``` * and their asynchronous versions ``` virtual std::future sayAsync(const Message& msg, WhenEnum when) = 0; ``` * an access to a publisher The interface does not contain signals defined in your interface, the signals feature is achieved with a subscriber/publisher mechanism. A class interested in notification on signal emission (and property changes) needs to use the publisher to subscribe for those events. The important thing is that your implementation of the interface should notify the `IHelloPublisher` about emitted signals or state changed. note Notice the convention for the getter - we use an underscore `_` - this function is added by the generator to your interface. The underscore is used to prevent name collision with your implementation of the interface. ``` virtual IHelloPublisher& _getPublisher() const = 0; ``` #### Publisher[​](#publisher "Direct link to Publisher") The `IHelloPublisher` provides the interface for subscribing and unsubscribing from a signal emission or a property change. Use of such a class is mandatory for implementation and allows users of your API to react on changes. The publisher is responsible for keeping its clients informed about requested changes. But it is the interface (here `IHello`) implementation responsibility to call the publish functions from its API when signal or property change occurs e.g: ``` void Hello::setLast(const Message& last) { ... // call the publish function m_publisher->publishLastChanged(last); } ``` There are two ways of subscribing for changes. Either provide an implementation for `ISubscriber` interface class and use is with those publisher's functions ``` void subscribeToAllChanges(IHelloSubscriber& subscriber); void unsubscribeFromAllChanges(IHelloSubscriber& subscriber); ``` The subscribe ISubscriber assures your subscriber will be informed about all emitted signals and all property changes. The other option is to use the *parallel* system of notification which doesn't require the `ISubscriber` and informs your Interface user about change of specific property or signal emission. E.g for `LastChanged` property we have: ``` long subscribeToLastChanged(HelloLastPropertyCb callback); // returns handleId that needs to be used to unsubscribe void unsubscribeFromLastChanged(long handleId); ``` The publisher will execute the provided callback function each time and only when this property is changed. caution For both methods of subscribing make sure to remove subscription for a ISubscriber/callback before it becomes invalid. Also make sure that the subscribed function is not blocking and returns immediately! The publish functions need to be called by the implementation of the `IHello` on each property change or signal emission to notify all the subscribers about this change. note Subscriptions systems are parallel - if you use both of them for single interface user i.e subscribe your class as an `ISubscriber` and for a single change your subscriber will be informed twice about that change, one for each subscription mechanism. This means that the unsubscribe functions for both mechanisms work only for that mechanism, i.e. the unsubscribe with `ISubscriber` will not remove any subscriptions for the specific property/signal and they stay intact. #### Subscriber[​](#subscriber "Direct link to Subscriber") For each interface we generate the `ISubscriber`, for example here the `IHelloSubscriber` which contains a set of functions for all the properties and signals for the notification mechanism. The implementation for `IHello` contains a publisher which can be used to subscribe for those changes called `IHelloSubscriber`. Each time publisher will receive publish request it will notify all subscribed `IHelloSubscriber` object about change. You can use `IHelloSubscriber` class to implement your local clients of the `IHello` or a network service adapter - see [Olink Server](/template-cpp14/docs/features/olink.md#olink-server-adapter). Below is a small code snippet which shows an example implementation for `IHelloSubscriber`, a class which main functionality is reacting on changes in `Hello`. ``` class HelloUser : public IHelloSubscriber { public: HelloUser(IHello& Hello) : m_Hello(Hello) { m_Hello._getPublisher().subscribeToAllChanges(*this); } ~HelloUser() { m_Hello._getPublisher().unsubscribeFromAllChanges(*this); } void onJustSaid(const Message& /*msg*/) override { // do something with msg. } void onLastChanged(const Message& /*last*/) override { // do something with last; } // Some other functionality. private: IHello& m_Hello } ``` ### Other[​](#other "Direct link to Other") `📜 CMakeLists.txt` for building this module, describing it's dependencies, and exposing built package for other features that will use it.
`📜 common.h` prepares dll import/export statements. --- # Conan package manager With this feature you we generate a conan support for your project. It helps with packaging and to managing dependencies for different platforms, OSs, compilers, architecture, cross-builds. Read more about conan and its all benefits [here](https://conan.io/). ### File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With an example API Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` The files for conan will be generated for project: ``` 📂hello-world ┣ 📂apigear ┣ 📂cpp_hello_world ┃ ┣ 📂apigear ┃ ┃ ... ┃ ┃ ┗ 📜conanfile.py ┃ ┣ 📂examples ┃ ┃ ┣ 📂 app ┃ ┃ ┃ ... ┃ ┃ ┃ ┗ 📜conanfile.txt ┃ ┃ ... ┃ ┣ 📂modules ┃ ┃ ┗ 📂io_world ┃ ┃ ┃ ┣ 📂conan ┃ ┃ ┃ ┃ ┣ 📂test_package ┃ ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┃ ┣ 📜conanfile.py ┃ ┃ ┃ ┃ ┃ ┗ 📜main.cpp ┃ ┃ ┃ ┃ ┗ 📜conanfile.py ┣ 📂scripts ┃ ┣ 📜test_conan.bat ┃ ┣ 📜test_conan.sh ``` ### Run scripts[​](#run-scripts "Direct link to Run scripts") The top level files you'll find in '📂hello-world/cpp\_hello\_world' `📜test_conan.bat` (for windows) and `📜test_conan.sh` (for linux based systems) contain set of command line commands to build whole project. The scripts build the project twice: * Firstly, using a package development commands. This way is recommended for local development, testing and modifications/customization of the build process in the user space without recompiling the entire package. Used command is `conan build ..` (preceded by `source` command that copies necessary sources and `install` command which installs the requirements and generates the info files). You can read more about build [here](https://docs.conan.io/1/reference/commands/development/build.html). * Secondly, with one of conan creator commands `conan create ...`. It is primarily used to generate, package, and upload packages to a repository. It calls the local conanfile.py ‘package()’ method. The `conanfile.py` is located in the `📂modules` and created for each module separately. Creator Commands are more geared toward recipe creation, versioning, and repository interactions. You can read more about package [here](https://docs.conan.io/1/reference/commands/development/package.html). We use CMake build system with conan. tip Modify the script to suit your needs. You can choose one of the building methods depending on current stage of your package development lifecycle. Some of the above files, the `📂cpp_hello_world/apigear/conanfile.py` or `conanfiles.txt` generated for each example were present even without conan feature - for simplicity of generation rules.
The `📂cpp_hello_world/apigear/conanfile.py` contains recipe for building additional library - network layer for cpp-template, independent from your API, and necessary for [ObjectLink core](https://github.com/apigear-io/objectlink-core-cpp), for aligning to MQTT protocol or for tracing.
The `📂cpp_hello_world📂modules/io_world/conan/conanfile.py` is the main recipe for your API module, which contains all the info necessary for packaging. Dependencies for the examples are stored in conanfile.txt files in each of the examples ( conanfile.txt is a simplification of conanfile.py, that can be used exclusively to consume dependencies, but not to create packages). The test package Files inside the `📂cpp_hello_world/modules/io_world/conan/test_package contain a test package`. It instantiates `Hello` implementation from a `stub` feature. It is a check that package for `io_world` was created in proper way, can be found and used. --- # Monitor Use the monitor feature to examine the interface calls, state and signals during runtime. With this feature you obtain a monitor client and a monitored version of your interfaces. The monitoring server is embedded into the [ApiGear Studio](/docs/studio/intro.md) and [CLI version](/docs/cli/intro.md). More details on [monitoring](/docs/monitor/intro.md) ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With our example API definition Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure will be generated. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┃ ... ┣ 📂cpp_hello_world ┃ ┣ 📂apigear ┃ ┃ ... ┃ ┃ ┣ 📂tracer ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┣ 📜tracer.cpp ┃ ┃ ┃ ┣ 📜tracer.h ┃ ┃ ┃ ┗ 📜tracer.test.cpp ┃ ┣ 📂examples ┃ ┣ 📂modules ┃ ┃ ┗ 📂io_world ┃ ┃ ┃ ┣ 📂generated ┃ ┃ ┃ ┃ ┣ 📂monitor ┃ ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.tracedecorator.cpp ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.tracedecorator.h ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.tracer.cpp ┃ ┃ ┃ ┃ ┃ ┗ 📜hello.tracer.h ... ``` ### Interface Independent Trace Client[​](#interface-independent-trace-client "Direct link to Interface Independent Trace Client") When generating the monitor feature (or olink feature) you'll get an additional folder in the top most directory:`📂hello-world/apigear`. The `📂tracer` subfolder contains a client for logging, with a network layer (based on the [POCO](https://docs.pocoproject.org/current/Poco.Net.html) library). The class connects to the `Apigear Trace Server` and takes care of formatting and sending the traces. ### Monitor[​](#monitor-1 "Direct link to Monitor") Files `📜hello.tracedecorator.h` and `📜hello.tracedecorator.cpp` contain the trace wrapper for the `Hello` interface. The wrapper will be generated for each interface inside the API definition. ``` class HELLO_WORLD_EXAMPLE_IO_WORLD_EXPORT HelloTraceDecorator : public IHello, public IHelloSubscriber { explicit HelloTraceDecorator(IHello& impl, ApiGear::PocoImpl::Tracer& tracer) ... }; ``` The tracer uses the implementation of `IHello` - wraps each interface operation call and the notifications of property changed and signal emitted with a lgo to a monitor tool. The other calls are just forwarded to your implementation - so you can use the `HelloTraceDecorator` as a `Hello` object. note The wrapped object `IHello& impl` is just the object that fulfills the `IHello` interface, so the implementation can be e.g. the [`OlinkClient`](/template-cpp14/docs/features/olink.md) version of `Hello`. The `📜hello.tracer.h` and `📜hello.tracer.cpp` files contain helper to prepare interface specific trace data for the general tracing [TraceClient](/template-cpp14/docs/features/monitor.md#tracing-example) ### Tracing example[​](#tracing-example "Direct link to Tracing example") Prepare an application (generate for both examples and monitor features) that uses the tracer: ``` int main(){ ApiGear::PocoImpl::Tracer tracer; tracer.connect("http://localhost:8182", "testExampleApp"); std::unique_ptr helloImplementation = std::make_unique(); std::unique_ptr tracedHello = IoWorld::HelloTraceDecorator::connect(*helloImplementation, tracer); // use your tracedHello as it was Hello implementation, all property changes, and signals and method execution and function calls will be traced. auto lastMessage = tracedHello->getLast(); tracedHello->say(lastMessage, IoWorld::WhenEnum::Soon); IoWorld::Message someMessage("the new content"); tracedHello->setLast(someMessage); tracedHello->_getPublisher().publishJustSaid(someMessage); return 0; } ``` Make sure that you are using the same port for sending and receiving traces. For sending: check yor setting for `ApiGear::PocoImpl::Tracer`. For receiver: go to the ApiGear Studio settings and set correct port number. By default the address is set to `ws://localhost:8182/ws`. --- # MQTT caution This is an experimental feature. It contains the smallest working set of functionalities to adapt the generated interface for use with the MQTT protocol. It doesn't include any security, the error handling is minimal and it is not production ready. Please also check issues on github for this template. note A [PAHO](https://eclipse.dev/paho/index.php?page=clients/python/index.php) library is used to implement MQTT in this library. It seems to have significant performance limitations for high load. If you need to use MQTT in high load application consider using our qt template - currently seems to be working much better. This feature does not only introduce MQTT protocol into your project, but also show that an existing protocol can be adapted for sharing your data in your ecosystem. When going through this document you may notice this implementation contains general client/server adapters in `📂hello-world/apigear/mqtt` and an interface specific part generated from templates for each interface in `📂hello-world/cpp_hello_world/modules/io_world/generated/mqtt`. This feature provides a *client* and *service* adapter for your interfaces for the MQTT protocol. It allows you to connect different applications in the same or different technologies (check all of our [templates](/docs/sdk/intro.md)). * Use an *Mqtt client* instead of your interface implementation to be able to receive data from remote service. * Use an *Mqtt server adapter* to expose your interface implementation as a remote service. tip The MQTT broker is not provided with the template. To be able to run client and service you need to run a broker of your choice, which is accessible for both client and service. caution In this library we use STL classes in public interfaces. We assume that this library is built from source (configuration provided by generated `CMakeLists`). Otherwise, it has to be ensured that your build configuration matches the library build configuration. ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With our API definition Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure is generated. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┃ ... ┣ 📂cpp_hello_world ┃ ┣ 📂apigear ┃ ┃ ┣ 📂mqtt ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┣ 📜mqttclient.cpp ┃ ┃ ┃ ┣ 📜mqttclient.h ┃ ┃ ┃ ┣ 📜mqttservice.cpp ┃ ┃ ┃ ┣ 📜mqttservice.h ┃ ┃ ┃ ┣ ... (helper files) ┃ ┃ ... ┃ ┣ 📂examples ┃ ┣ 📂modules ┃ ┃ ┗ 📂io_world ┃ ┃ ┃ ┣ 📂generated ┃ ┃ ┃ ┃ ┣ 📂mqtt ┃ ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┃ ┣ 📜helloclient.cpp ┃ ┃ ┃ ┃ ┃ ┣ 📜helloclient.h ┃ ┃ ┃ ┃ ┃ ┣ 📜helloservice.cpp ┃ ┃ ┃ ┃ ┃ ┗ 📜helloservice.h ... ``` ### ApiGear MQTT - The Network Layer[​](#apigear-mqtt---the-network-layer "Direct link to ApiGear MQTT - The Network Layer") When using the `mqtt` feature (or any of those: `olink`, `monitor` feature) you'll get an additional folder at the top most directory: the `📂hello-world/cpp_hello_world/apigear`. The `📂mqtt` subfolder contains objects that implement a network layer (based on [PAHO](https://eclipse.dev/paho/index.php?page=clients/python/index.php) library) for the MQTT protocol. Those are: * `Client` - Adapts the MQTT client, to serve as an network endpoint for [interface client adapters](/template-cpp14/docs/features/mqtt.md#mqtt-client-adapter). Exposes: * methods that allow receiving data from remote service: subscribing for properties changes, signals emission and method response invocation; * methods that allow remote using the service: requesting property change or invoking a method. The client may serve many client interface adapters, even for the same interfaces (allows subscribing for same topic). In case many interface client adapters for same interface are connected: property changes and signals are provided to all the interface client adapters, but the invoke method response will be delivered only for the one that requested it. * `ServiceAdapter` - Adapts the MQTT client to serve as an network endpoint for [interface service adapters](/template-cpp14/docs/features/mqtt.md#mqtt-server-adapter). Exposes: * methods that allow receiving requests from remote clients: subscribing for properties change requests, send method invocation; * methods that allow publishing property change, signal, functionality to handles sending a response for method invocation requests. This `ServiceAdapter` may be used for many interface service adapters, but it is not recommended to use more than one interface service adapter for same interface. tip Have in mind that MQTT might not be suitable for high-frequency messages especially with one mqtt client serving more than one object. Also the brokers have limits for messages number/size queued from one client. In case you are not getting all the messages consider changing those or splitting traffic between more clients (maybe some handle the properties, some handle the methods). ### MQTT Client Adapter[​](#mqtt-client-adapter "Direct link to MQTT Client Adapter") Files `📜helloclient.h` and `📜helloclient.cpp` implement the remote client for the `Hello` interface - a `HelloClient` class.
The object is an `IHello` implementation.
It requires an instance of Apigear::Mqtt::Client to work. It uses the Client to subscribe (and unsubscribe) for topics that allow receiving properties, signals and invoke responses from service. #### Properties[​](#properties "Direct link to Properties") The property getters (here `getLast()` ) return immediately the locally stored,last received value from service.
The property setter (here `void setLast(const Message& last)` ) requests setting a value on service side, local value is not changed.
You can subscribe to a property change event (here `last` property ) through the publisher which you can get from the API with `_getPublisher()`. Or you can subscribe as an `IHelloSubscriber` and get all changes. See [publisher](/template-cpp14/docs/features/api.md#publisher) and [subscriber](/template-cpp14/docs/features/api.md#subscriber) for more info on subscribing. When the client receives information that server changed the property, a target property (here `last`) is updated locally and a notifies subscribers that property has changed. note The connected interface client adapter has its local properties in sync with a service. The messages with property changes are retained in MQTT broker, so all properties which are already set are provided. #### Operations[​](#operations "Direct link to Operations") The operations have an async version, which is called by the immediate version. The async version sends an invoke operation request to a server. So calling `myHelloClientInstance.say(myWhen)` implements execution of `sayAsync` and waits for the result (for non-void type of operations). Have in mind that this is a blocking operation. #### Signals[​](#signals "Direct link to Signals") You should not emit any signals from a client. You can subscribe to any signals offered by your interface (here `justSaid)` signal), through the publisher. You can either select the signal you're interested in, or subscribe as an `IHelloSubscriber` to get all the signals and property change notifications. When a `HelloClient` client receives the message from server that indicates the signal was emitted it notifies all the subscribers that requested this notification.. #### Use `HelloClient`[​](#use-helloclient "Direct link to use-helloclient") `HelloClient` is an adapter of Mqtt (with protocol and network layer implementation), here provided by a `ApiGear::MQTT::Client`. All you need to do is to pass the `ApiGear::MQTT::Client` to your Interface Client Adapter, and request connecting to host when it is convenient for you. ``` // Create a client and make a connection auto mqttclient = std::make_shared("UniqueClientName"); // set up modules auto ioWorldHello = std::make_unique(mqttclient); // start mqtt connection mqttclient.connectToHost("tcp://localhost:1883"); // Use the same port number as your broker is using. Typically, without any other settings it is "1883". // use your ioWorldHello as it was Hello implementation ioWorldHello->say(IoWorld::WhenEnum::Soon); auto lastMessage = ioWorldHello->getLast(); IoWorld::Message someMessage("the new content"); ioWorldHello->setLast(someMessage); testIoWorldHello->_getPublisher().subscribeToJustSaid([](auto args) { /*handle the signal*/}); ``` ### MQTT Server Adapter[​](#mqtt-server-adapter "Direct link to MQTT Server Adapter") The files `📜helloservice.h` and `📜helloservice.cpp` contain the MQTT service side adapter for the `Hello` interface - the `HelloService` class. It uses the general MQTT service adapter `ApiGear::MQTT::Service`. `HelloService` object exposes the local object for remote usage with the MQTT protocol. It handles all the network requests, and calls on your local object. The client connections and communication with them is handled transparently for you, no additional actions are needed. #### Properties[​](#properties-1 "Direct link to Properties") Each time a property of your the `Hello` interface implementation changes, a message is sent to all connected clients with information, which property changed to which value. It is true for both - local calls on the `Hello` object, by other local `Hello` users or when a change property request message is received by this `HelloService` (the `HelloService` applies the property on your local `Hello` object and then the `HelloService` is triggered). #### Operations[​](#operations-1 "Direct link to Operations") The operations invocation which was triggered from the clients through the network will be performed on your local `Hello` object. The result of the operation (if any) will be returned only to the client, from which the message was send, not all clients. #### Signals[​](#signals-1 "Direct link to Signals") All the signals emitted by your local `Hello` objects are forwarded as an MQTT message to all connected clients. note Have in mind that the `Hello` implementation is not thread safe by default. #### Use `HelloService`[​](#use-helloservice "Direct link to use-helloservice") `HelloService` is an adapter of Mqtt (with protocol and network layer implementation), here provided by a `ApiGear::MQTT::ServiceAdapter` All you need to do is to pass the `ApiGear::MQTT::ServiceAdapter` and a local Hello implementation to your HelloService Adapter, then request connecting to host whenever convenient for you. ``` auto mqttservice = std::make_shared("ServiceUniqueNameInMqtt"); auto ioWorldHello = std::make_shared(); IoWorld::MQTT::HelloService mqttHelloService(ioWorldHello, mqttservice); // start mqtt connection mqttservice->connectToHost("tcp://localhost:1883"); // use your ioWorldHello as it was Hello implementation, all property changes, and signals will be passed to connected MqttHello clients. ioWorldHello->say(IoWorld::WhenEnum::Soon); IoWorld::Message someMessage("the new content"); auto lastMessage = ioWorldHello->getLast(); ioWorldHello->setLast(someMessage); // after this call - if new property is different than current one - all clients will be informed about new value. testIoWorldHello->_getPublisher().publishJustSaid(someMessage); ``` ### MQTT Messages[​](#mqtt-messages "Direct link to MQTT Messages") For the MQTT topic structure and payload format, see **[ApiGear over MQTT](/docs/protocols/mqtt/mapping.md)**. --- # OLink This feature provides a *client* and a *server* adapter for your interfaces for the [ObjectLink](/docs/protocols/objectlink/intro.md) protocol. It allows you to connect different applications using the same or different technologies (check all of our [templates](/docs/sdk/intro.md)). Use an *OLink client* instead of your interface implementation to connect to a remote service, or to a the [ApiGear simulation](/template-cpp14/docs/features/olink.md#simulation). Use an *OLink server adapter* to expose your interface implementation as a remote service. Below you'll find short introduction on `ApiGear ObjectLink` protocol. Before details on client and server, you'll also find piece of information on a network layer implementation for handling ObjectLink in `template-cpp`. note The `OLink client` and the `OLink server` objects are ready-to-use as they are, they don't' need any extra implementation. If you want quickly start working with them go to use sections of [client](/template-cpp14/docs/features/olink.md#olink-client-adapter), [server](/template-cpp14/docs/features/olink.md#olink-server-adapter). ### Apigear ObjectLink protocol and ObjectLink core library[​](#apigear-objectlink-protocol-and-objectlink-core-library "Direct link to Apigear ObjectLink protocol and ObjectLink core library") The [ObjectLink](/docs/protocols/objectlink/intro.md) protocol is a lightweight websocket based protocol for Objects described with an interface. It allows connecting a client object with a server object, and perform remote operations like: remote property change request (client) or notifications on property changed (server), inform about signal emission (server) and allows requesting a remote method call (client) and delivering a response to the caller (server). The Olink feature for your interface uses a library [ObjectLink core](https://github.com/apigear-io/objectlink-core-cpp), common for cpp based templates. The provided `CMakeLists` already contain all the dependencies, so you don't have to add it manually. The library provides an abstraction layer for handling the protocol, independent of the actual network stack. It encodes and decodes the messages and delivers them to corresponding objects identified by a registry. The setup is minimal: instantiate the registry (for client and/or server) and pass them to network layer objects in [cpp apigear library](/template-cpp14/docs/features/olink.md#cpp-apigear---the-network-layer) caution In this library we use STL classes in public interfaces. We assume that this library is built from source (configuration provided by generated CMakeLists). Otherwise, it has to be ensured that your build configuration matches the library build configuration. ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With out example API Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure will be generated. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┃ ... ┣ 📂cpp_hello_world ┃ ┣ 📂apigear ┃ ┃ ┣ 📂mqtt ┃ ┃ ┣ 📂olink ┃ ┃ ┃ ┣ 📂private ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┣ 📜olinkconnection.cpp ┃ ┃ ┃ ┣ 📜olinkconnection.h ┃ ┃ ┃ ┣ 📜olinkhost.cpp ┃ ┃ ┃ ┣ 📜olinkhost.h ┃ ┃ ┃ ┣ ... (helper files) ┃ ┃ ... ┃ ┣ 📂examples ┃ ┣ 📂modules ┃ ┃ ┗ 📂io_world ┃ ┃ ┃ ┣ 📂generated ┃ ┃ ┃ ┃ ┗ 📂olink ┃ ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┃ ┣ 📜helloclient.cpp ┃ ┃ ┃ ┃ ┃ ┣ 📜helloclient.h ┃ ┃ ┃ ┃ ┃ ┣ 📜helloservice.cpp ┃ ┃ ┃ ┃ ┃ ┗ 📜helloservice.h ┃ ┣ 📜 Lifecycle for olink client-server implementation.md ... ``` note You may notice an extra document: `📜Lifecycle for olink client-server implementation.md` which explains life cycle of main objects used when using an olink protocol, in a form of flow diagrams. ### cpp ApiGear - The Network Layer[​](#cpp-apigear---the-network-layer "Direct link to cpp ApiGear - The Network Layer") When generating the olink feature (or monitor feature) you'll get an additional folder at the top most directory: the `📂hello-world/cpp_hello_world/apigear`. The `📂olink` subfolder contains objects that implement a network layer (based on [Poco](https://docs.pocoproject.org/current/Poco.Net.html) library) for the ObjectLink protocol. Those are: * `OlinkClient` - the client, that serves your [interface client adapters](/template-cpp14/docs/features/olink.md#olink-client-adapter). The class is responsible for network connection for ObjectLink client side. It uses the ObjectLink core library for handling messages according to the ObjectLink protocol. Handles linking and unlinking with a remote service for the sink added by the user (the interface OlinkClient Adapters), with regard to the connection state. All the messages dedicated to your object will be delivered to the adapter and proper actions will be performed for the clients that use it, without any of your actions. Also the OlinkClient holds a message queue for messages during disconnected time. * `OlinkHost` and a helper class `OlinkRemote` that you'll find in `📂private` subfolder The server, which hosts your services exposed to network through the [interface server adapters](/template-cpp14/docs/features/olink.md#olink-server-adapter). The class is responsible to set up the server and manage connections requested by clients. It uses ObjectLink core library for handling messages according to the ObjectLink protocol. All incoming messages will be translated to calls and routed to your local object. ### Olink Client Adapter[​](#olink-client-adapter "Direct link to Olink Client Adapter") Files `📜helloclient.h` and `📜helloclient.cpp` contain the olink client version of the `Hello` interface - an `HelloClient` class. It implements two interfaces: `IHello` and a `IObjectSink`. IObjectSink (click to expand) ``` class IObjectSink { public: virtual std::string olinkObjectName() = 0; virtual void olinkOnSignal(const std::string& signalId, const nlohmann::json& args) = 0; virtual void olinkOnPropertyChanged(const std::string& propertyId, const nlohmann::json& value) = 0; virtual void olinkOnInit(const std::string& objectId, const nlohmann::json& props, IClientNode* node) = 0; virtual void olinkOnRelease() = 0; }; ``` The `IObjectSink` interface comes from [ObjectLink core](https://github.com/apigear-io/objectlink-core-cpp) and is necessary for handling incoming messages from the server side and is used by a `OLinkClient` ([cpp Apigear Olink lib](/template-cpp14/docs/features/olink.md#cpp-apigear---the-network-layer)). Apart from setting up and tear down of the `OLinkHello` object, you don't need to perform any additional actions, the object `OLinkHello` will communicate with the server transparently and you should use it as an `IHello` Object only. #### Properties[​](#properties "Direct link to Properties") The property getters (here `Message last()` ) return immediately the locally stored last received value from server. The property setter (here `void setLast(const Message& last)` ) requests setting a value on server side, local value is not changed. You can subscribe to a property changed (here `last` property ) through the publisher you can get from the `HelloClient` with `_getPublisher()`. Or you can subscribe (instead of the above) for as an IHelloSubscriber to receive all changes (and signals). When the client receives information that server changed the property, a target property (here `last`) is updated locally and a notifies subscribers that property has changed. note The connected client has its local properties initialized - on a successful linking client with server the client receives a message with current state of properties of the service on server side. #### Operations[​](#operations "Direct link to Operations") The operations have an additionally `async` version, which is called by the immediate version. The async version sends an invoke operation request to a server. So calling `myOlinkHelloInstance.say(myWhen)` implements execution of `sayAsync` and waits for the result (for non-void type of operations). Have in mind that this is a blocking operation. #### Signals[​](#signals "Direct link to Signals") You should not emit any signals from a client. You can connect to any signals offered by your interface (here `void justSaid(const Message& msg)` ), through the publisher. You can either select the signal you're interested in, or subscribe as an IHelloSubscriber to get all the signals and property change notifications. When a `HelloClient` client receives the message from server that indicates the signal was emitted it emits a signal (here `justSaid`). #### Use `HelloClient`[​](#use-helloclient "Direct link to use-helloclient") As mentioned earlier you need a network layer, here provided by a `ApiGear::ObjectLink::OLinkClient` which also contains the protocol handling `ClientNode`. All you need to do is give it a global `ApiGear::ObjectLink::ClientRegistry`, request connecting to host when it is convenient for you and then add your `HelloClient`. ``` ApiGear::ObjectLink::ClientRegistry registry; ApiGear::ObjectLink::OLinkClient client(registry); // Create a global registry. ApiGear::ObjectLink::ClientRegistry registry; // Create a client and make a connection ApiGear::PocoImpl::OlinkConnection client(registry); // Create your client and request linking, which will try to connect with a server side for this object. auto ioWorldHello = std::make_shared(); client.connectAndLinkObject(ioWorldHello); client.connectToHost(Poco::URI("ws://localhost:8182")); // You can try out properties auto lastMessage = ioWorldHello->getLast(); // Executing the methods ioWorldHello->say(lastMessage, IoWorld::WhenEnum::Soon); IoWorld::Message someMessage("the new content"); ioWorldHello->setLast(someMessage); // Or subscribe for signals. ioWorldHello->_getPublisher().subscribeToJustSaid([](auto args) { /*handle the signal*/}); // remember to unlink your object if you won't use it anymore. client.unlinkObjectSource(ioWorldHello->olinkObjectName()); ``` caution `ApiGear::ObjectLink::ClientRegistry` can have only one object with the same identifier. This means that, to have more than one `OlinkHello` objects you would need to put them in different registries. Each Object has an identifier which is created based on the Interface Name, this way client object can be matched with proper server object. ### Olink Server Adapter[​](#olink-server-adapter "Direct link to Olink Server Adapter") Files `📜helloservice.h` and `📜helloservice.h` contain the olink server adapter for the `Hello` interface - the `HelloService` class. It implements an `IObjectSource` interface (from [ObjectLink core](https://github.com/apigear-io/objectlink-core-cpp)), which wraps your `Hello` and exposes it for remote usage with the [ObjectLink](/docs/protocols/objectlink/intro.md) protocol. It handles all the network requests, and calls your local object. When creating the `HelloService` you need to provide the local `IHello` service object, you want to expose to clients. The `IObjectSource` interface: IObjectSource (click to expand) ``` class IObjectSource { public: virtual std::string olinkObjectName() = 0; virtual nlohmann::json olinkInvoke(const std::string& methodId, const nlohmann::json& args) = 0; virtual void olinkSetProperty(const std::string& propertyId, const nlohmann::json& value) = 0; virtual void olinkLinked(const std::string& objectId, IRemoteNode* node) = 0; virtual void olinkUnlinked(const std::string& objectId) = 0; virtual nlohmann::json olinkCollectProperties() = 0; }; ``` After setting up the OlinkHost (cpp Apigear Olink lib) and registering your Interface registered, the client connections and communication with them is handled transparently for you, no additional actions are needed. note All the received messages from the clients are handled in a thread in which the connection resides, this means that your object may be used from different threads (local and each connection). #### Properties[​](#properties-1 "Direct link to Properties") Each time a property change signal (here `void lastChanged(const Message& last)` ) is emitted, a message is sent to all connected clients. The message contains the information, which property changed and the corresponding value. This may occur either when you change a property directly on your local `Hello` object, or when a change property request message is received by the `HelloService`, which applies the property on your local `Hello` object. #### Operations[​](#operations-1 "Direct link to Operations") The remote operations invocation from the clients via the network will be performed on your local `Hello` object. The result of the operation (if any) will be returned only to the caller, not all clients. Have in mind that your object may be called from more that one thread. #### Signals[​](#signals-1 "Direct link to Signals") All the signals emitted by your local `Hello` objects are forwarded to all connected clients with an olink messages. #### Use `OLinkHelloAdapter`[​](#use-olinkhelloadapter "Direct link to use-olinkhelloadapter") As mentioned earlier you need a network layer, here provided by a `ApiGear::ObjectLink::OLinkHost` which also contains the protocol handling `RemoteNodes`. The `ApiGear::ObjectLink::OLinkRemote` objects are created automatically per connection. All you need to do is give a global `ApiGear::ObjectLink::RemoteRegistry` with your `OLinkHelloAdapter` in it to `ApiGear::ObjectLink::OLinkHost`. ``` ApiGear::ObjectLink::RemoteRegistry registry; auto logFunction = [](auto /*level*/, auto /*msg*/){ }; ApiGear::PocoImpl::OLinkHost testserver(registry,logFunction); auto ioWorldHello = std::make_shared(); auto ioWorldOlinkHelloService = std::make_shared(ioWorldHello, registry); registry.addSource(ioWorldOlinkHelloService); auto portNumber = 8000; // Start your server with your service added to registry. testserver.listen(portNumber); // use your ioWorldHello as it was Hello implementation, all property changes, and signals will be passed to connected OLink clients. auto lastMessage = ioWorldHello->getLast(); ioWorldHello->say(lastMessage, IoWorld::WhenEnum::Soon); IoWorld::Message someMessage("the new content"); ioWorldHello->setLast(someMessage); // after this call - if new property is different than current one - all clients will be informed about new value. testIoWorldHello->_getPublisher().publishJustSaid(someMessage); // remember to remove from registry if you won't use it anymore. registry.removeSource(ioWorldOlinkHelloService->olinkObjectName()); ``` ## Simulation[​](#simulation "Direct link to Simulation") The simulation can be used to test, demonstrate or develop applications without the need to have the actual service available. The simulation server is integrated into the [ApiGear studio](/docs/studio/intro.md) and the [CLI](/docs/cli/simulate.md). For simulating you will use [simulation scenarios](/docs/scripting/backends/scenario.md) They allow to define sequences of actions. The actions can change the property values of the service or emit signals. The scenarios can be written using a YAML schema. See more on [simulation](/docs/scripting/backends/intro.md). ### Prepare your application[​](#prepare-your-application "Direct link to Prepare your application") Make sure you are using OlinkClient as your `Hello` object, an instance on [cpp side](/template-cpp14/docs/features/olink.md#olink-client-adapter) You may try our example, it subscribes for signals and property changes, with a simple logging function. Create new folder under `examples` directory. Paste the below `main.cpp` and `CMakeLists.txt` files. Remember to add this directory to main `CMakeLists.txt` (or to extend test\_conan.bat/sh in case using conan). main.cpp (click to expand) ``` #include #include "io_world/generated/olink/helloclient.h" #include "apigear/olink/olinkconnection.h" #include "apigear/tracer/tracer.h" #include "apigear/olink/olinklogadapter.h" #include "olink/clientregistry.h" #include "olink/clientnode.h" #include "apigear/olink/olinkconnection.h" #include using namespace HelloWorldExample; int main() { // Create a global registry. ApiGear::ObjectLink::ClientRegistry registry; // Create a client and make a connection ApiGear::PocoImpl::OlinkConnection client(registry); // Create your client and request linking, which will try to connect with a server side for this object. auto ioWorldHello = std::make_shared(); client.connectAndLinkObject(ioWorldHello); client.connectToHost(Poco::URI("ws://localhost:8182")); // Or subscribe for signals. ioWorldHello->_getPublisher().subscribeToJustSaid([](auto& /*args*/) { std::cout << "Just said received" << std::endl; }); ioWorldHello->_getPublisher().subscribeToLastChanged([](auto& /*args*/) { std::cout << "Last Changed" << std::endl; }); bool keepRunning = true; std::string cmd; do { std::cout << "Enter command:" << std::endl; getline(std::cin, cmd); if (cmd == "quit") { keepRunning = false; client.disconnect(); } } while (keepRunning); client.disconnectAndUnlink(ioWorldHello->olinkObjectName()); return 0; } ``` CMAkeLists.txt (click to expand) ``` cmake_minimum_required(VERSION 3.1) project(OLinkClientSimuExample) # append local binary directory for conan packages to be found list(APPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set (SOURCES main.cpp ) add_executable(OLinkClientSimuExample ${SOURCES} ) # ensure maximum compiler support if(NOT MSVC) target_compile_options(OLinkClientSimuExample PRIVATE -Wall -Wextra -Wpedantic -Werror -fvisibility=hidden) else() target_compile_options(OLinkClientSimuExample PRIVATE /W4 /WX /wd4251) # disable the warning for getenv - needs better cross platform solution target_compile_definitions(OLinkClientSimuExample PRIVATE -D_CRT_SECURE_NO_WARNINGS) endif() find_package(io_world QUIET COMPONENTS io_world-core io_world-implementation io_world-monitor io_world-olink) target_link_libraries(OLinkClientSimuExample io_world::io_world-core io_world::io_world-implementation io_world::io_world-monitor io_world::io_world-olink ) install(TARGETS OLinkClientSimuExample RUNTIME DESTINATION bin COMPONENT Runtime) ``` note Make sure to configure the client to use the correct IP and port configuration of your simulation server e.g. set `client.connectToHost(QUrl("ws://127.0.0.1:8182/ws"));` and make sure your apigear simulation also uses this port. ### Prepare the scenario[​](#prepare-the-scenario "Direct link to Prepare the scenario") As a starting point you can check our simulation scenario: Scenario(click to expand) ``` schema: apigear.scenario/1.0 name: "first scenario" version: "1.0.0" #initial properties and setting gunction response interfaces: - name: io.world.Hello #( module io.world and interface Hello combination) properties: last: {content: "Initial"} operations: - name: say actions: - $return: { value: 88 } # sequence of changing properties and emitting signals sequences: - name: play with hello interval: 2000 # 2 seconds interface: io.world.Hello loops: 3 steps: # step is called every 2 secs according to interval - name: change property actions: - $set: { last: {content: "First Change of Property"} } - name: emit signal actions: - $signal: { justSaid: [ {content: "First Message"} ] } - name: change property AND emit signal actions: - $set: { last: {content: "Second Change of Property"} } - $signal: { justSaid: [ {content: "Other Signal"} ] } ``` Apart from the header with the scenario name and its version it consists of two parts: * initialization for each interface (here, our ui.world.Hello line 6), we're setting initial values of properties(line 8) and the return value of function calls (line 12) * sequence of events You can prepare more than one sequence. For each of them set interval between calling each action (line 16), set how many runs of that sequence you want to execute (line 18) and name the interface the action should be applied to (line 17). In each action you may change many properties, and emit signals. ### Run the simulation[​](#run-the-simulation "Direct link to Run the simulation") Put your scenario file in the `📂hello-world/apigear` along with other `.yaml` files. If you are using the studio, reload your project. Go to `settings` and make sure to set same the port as in your client app (here `8182`). Click the `play` button in the application in the `simulation` tab to start the simulation scenario, then start the client application. Instead of the ApiGear Studio application can also use its console version, run your scenario with command: ``` apigear simulate run path-from-cli-to-scenario/helloworldtest.scenario.yaml --addr :8182 ``` note We tried some measurements for the OLink performance. You may want to check the [our tests here](https://github.com/apigear-io/performance-checks). We're not providing any data of throughput and latency, as the branch is still in between alpha phase. In general we expect it to be fast as it has small overhead (which may be not sufficient for you). --- # Stubs The feature `stubs` adds: * a semi-featured starting point for your implementation and instantiable interfaces classes * an implementation for publishers * a setup for tests * a thread safe decorator for your interfaces With the stub feature you'll get an additional `core` folder generated, it contains common functionality used also by other features: Olink or Mqtt. Its content will be explained below. note For the basic skeleton implementation you'll find in `📂hello-world\cpp_hello_world\modules\io_world\implementation` the bear minimum classes are the [publisher](/template-cpp14/docs/features/stubs.md#publisher) and [data](/template-cpp14/docs/features/stubs.md#data) from `core` features. For the start focus on them and then explore more functionality. ### File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With our example API definition Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure will be generated. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┣ 📂cpp_hello_world ┃ ┣ 📂apigear ┃ ┣ 📂examples ┃ ┣ 📂modules ┃ ┃ ┗ 📂io_world ┃ ┃ ┃ ┣ 📂generated ┃ ┃ ┃ ┃ ┣ 📂api ┃ ┃ ┃ ┃ ┣ 📂core ┃ ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.data.h ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.publisher.cpp ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.publisher.h ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.threadsafedecorator.cpp ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.threadsafedecorator.h ┃ ┃ ┃ ┃ ┃ ┣ 📜io_world.json.adapter.cpp ┃ ┃ ┃ ┃ ┃ ┣ 📜io_world.json.adapter.h ┃ ┃ ┃ ┃ ┃ ┗ 📜io_world.test.cpp ┃ ┃ ┃ ┣ 📂implementation ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┣ 📜hello.cpp ┃ ┃ ┃ ┃ ┣ 📜hello.h ┃ ┃ ┃ ┃ ┗ 📜hello.test.cpp ... ``` ### Implementation[​](#implementation "Direct link to Implementation") Files `📜hello.h` and `📜hello.cpp` contain the implementation of the `IHello`. The class skeleton: * adds the properties of the interface as a private class members - with a [HelloData structure](/template-cpp14/docs/features/stubs.md#data) * implements getters and setters for each property * owns a [publisher](/template-cpp14/docs/features/stubs.md#publisher) and shares it through the `_getPublisher` method implementation * provides empty implementation of operation for you to fill the business logic. tip When adding a logic don't forget to use the publisher each time you want the property change to be shared or the signal to be emitted. note We generate the import/export statements, here HELLO\_WORLD\_EXAMPLE\_IO\_WORLD\_EXPORT, for all the classes and structs that have an implementation in a *cpp* file and may be used outside of the library. ### Core[​](#core "Direct link to Core") #### Publisher[​](#publisher "Direct link to Publisher") Files `📜hello.publisher.cpp` and `📜hello.publisher.h` provide an implementation of a IHelloPublisher (api.md#Publisher). It stores clients of the Hello (either the `IHelloSubscriber`s or callbacks for specific change). When a call of the appropriate publish function is executed on the publisher instance, it informs all of interested clients about the change. This class is thread safe in a way that adding and removing subscribers (both for full interface or specific notifications) is thread safe. If you use publisher from many threads for notification you need to make sure that `ISubscriber` or the callbacks are thread safe or modify the publisher to use them in thread-safe way. You may also want to take a look at generated [IHello thread safe decorator](/template-cpp14/docs/features/stubs.md#thread-safe-decorator). #### Data[​](#data "Direct link to Data") The `📜hello.data.h` contains the helper structure for implementations of Hello. It stores all the properties that an interface has and initializes them to default values. #### Thread Safe Decorator[​](#thread-safe-decorator "Direct link to Thread Safe Decorator") Files `📜hello.threadsafedecorator.cpp` and `📜hello.threadsafedecorator.h` provide a wrapper for your `IHello` implementation which can be used to make property access thread safe. For each property it allows multiple get operations at the same time but only one set. The properties are guarded one by one - so at one time you can write to different properties. Operations are not guarded by default - the implementation may either be thread safe by design (const, re-entrant) or too complex to simply lock it. So depending on the logic in your methods you can decide to leave them without any mutexes, add same mechanism as for properties in the implementation file of the Hello interface, or try different solutions like introducing an event queue. #### Json adapters[​](#json-adapters "Direct link to Json adapters") Files `📜io_world.json.adapter.cpp` and `📜io_world.json.adapter.h` are generated per whole module. They prepare adapters for structs to easy convert to and from `nlohmann::json`. This is currently used by our implemented protocols for the data packing. With this implementation you use the structures as below ``` Message message = json.get(); nlohmann::json message = message; ``` ### Tests[​](#tests "Direct link to Tests") For each interface we provide a template for your tests: `📜hello.test.cpp`. We used [Catch2](https://github.com/catchorg/Catch2) for setting up a test skeleton. You can find there instantiating the interface, section with executing methods (no checks) and section with setting a property with check that it actually happen. That is a starting point for your tests. tip In case you'll need mocks, `Catch2` goes well along with [trompeloeil](https://github.com/rollbear/trompeloeil). --- # Template C++ 14 (Maintenance) This is the documentation for the *cpp14* template for the [ApiGear](/docs/guide/quick-start.md) code generator. This template is in maintenance mode. The development moved to template for *cpp17* template and it is recommended to use it for new project. It is split in several parts: * [Quick-Start](/template-cpp14/docs/quickstart.md?current-template=template-cpp14) is the easiest way to get started * [Features](/template-cpp14/docs/features.md) explains the available code generator features and their usage and purpose. --- # Quick-Start The Quick-Start guide explains how in a few steps you get from an API definition to a functional c++ example. Steps one and two are universal for other technologies. In the step two you will choose a concrete *cpp14* template. For more general information about first steps with ApiGear [First Steps](/docs/guide/quick-start.md) The quick start enables only basic features: the [api](/template-cpp14/docs/features/api.md) generation and simple [stub](/template-cpp14/docs/features/stubs.md) implementation. For all available features check the [overview](/template-cpp14/docs/features.md). ## 1. Install the Code Generator[​](#1-install-the-code-generator "Direct link to 1. Install the Code Generator") ApiGear provides two powerful tools: a Studio and a Command Line Interface (CLI). The Studio is a graphical tool for creating, editing API modules and projects, while the CLI is a command-line tool for generating code from API modules. Both offer comprehensive functionality, including SDK template management, API project creation, code generation, API monitoring, and API simulation. [Download Studio](https://github.com/apigear-io/studio/releases/latest) [Download CLI](https://github.com/apigear-io/cli/releases/latest) ### Install ApiGear Studio[​](#install-apigear-studio "Direct link to Install ApiGear Studio") ApiGear Studio is available for macOS, Windows, and Linux. You can download it from the [ApiGear Studio GitHub releases page](https://github.com/apigear-io/studio/releases/latest). For more detailed information on ApiGear Studio, refer to the dedicated [studio documentation](/docs/studio/intro.md). ### Install ApiGear CLI[​](#install-apigear-cli "Direct link to Install ApiGear CLI") The ApiGear CLI is a versatile command-line tool for generating code from API modules. It's available for macOS, Windows, and Linux. Download the latest version from the [ApiGear CLI GitHub releases page](https://github.com/apigear-io/cli/releases/latest). The CLI offers all the features available in the Studio. For more detailed information on the ApiGear CLI, consult the dedicated [CLI documentation](/docs/cli/intro.md). ## 2. Obtain the Template[​](#2-obtain-the-template "Direct link to 2. Obtain the Template") There are several methods to obtain the template: installation via the Studio, installation via the CLI, or cloning/downloading from GitHub. This step is optional The ApiGear Studio and the CLI detect the specified template in the [solution document](#solution-file) and install it automatically. Optional steps (click to expand) ### Installation via CLI[​](#installation-via-cli "Direct link to Installation via CLI") When using the CLI, only the highlighted line needs to be executed. You can verify the successful installation using the `template cache` command. * Unreal Engine * C++14 * Qt6 * Python ``` $ apigear template install apigear-io/template-unreal@v3.2.2 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-unreal@v3.2.2 | https://github.com/apigear-io/template-unreal.git | | v3.2.2 ... ``` ``` $ apigear template install apigear-io/template-cpp14@v3.6.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-cpp14@v3.6.0 | https://github.com/apigear-io/template-cpp14.git | | v3.6.0 ... ``` ``` $ apigear template install apigear-io/template-qtcpp@v0.4.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-qtcpp@v0.4.0 | https://github.com/apigear-io/template-qtcpp.git | | v0.4.0 ... ``` ``` $ apigear template install apigear-io/template-python@v1.0.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-python@v1.0.0 | https://github.com/apigear-io/template-python.git | | v1.0.0 ... ``` ### Installation via Studio[​](#installation-via-studio "Direct link to Installation via Studio") Installing the template through the Studio is straightforward: * Unreal Engine * C++14 * Qt6 * Python 1. Open an existing project or create a new one 2. Navigate to the `Templates` tab 3. Click `Install` on the `apigear-io/template-unreal` entry 1) Open an existing project or create a new one 2) Navigate to the `Templates` tab 3) Click `Install` on the `apigear-io/template-cpp14` entry 1. Open an existing project or create a new one 2. Navigate to the `Templates` tab 3. Click `Install` on the `apigear-io/template-qtcpp` entry 1) Open an existing project or create a new one 2) Navigate to the `Templates` tab 3) Click `Install` on the `apigear-io/template-python` entry ![Installing the template](/img/apigear-studio-install-unreal-template.png) Figure: Installing the template ### Cloning from GitHub[​](#cloning-from-github "Direct link to Cloning from GitHub") If you need to inspect or modify the template's source code, cloning or downloading the repository is recommended. The repository doesn't need to be part of your project and can be stored anywhere on your computer. * Unreal Engine * C++14 * Qt6 * Python ``` $ git clone https://github.com/apigear-io/template-unreal.git ``` ``` $ git clone https://github.com/apigear-io/template-cpp14.git ``` ``` $ git clone https://github.com/apigear-io/template-qtcpp.git ``` ``` $ git clone https://github.com/apigear-io/template-python.git ``` You can then configure the solution file to use your template by providing the relative path from the solution file to the template directory. ## 3. Set Up the Project[​](#3-set-up-the-project "Direct link to 3. Set Up the Project") A typical project requires two files: a solution file specifying the APIs and the template to use, and at least one API module file. Ideally, both should be placed in a folder named `apigear`, adjacent to each other. Alternatively, you can use the Studio to create a new project and modify the two example files provided. ### Solution File[​](#solution-file "Direct link to Solution File") Create a [solution](/docs/guide/quick-start.md#solution-file) file. The example below specifies: * Module files in *line 8*, here the `helloworld.module.yaml` module with the `Hello` API * The output directory for generated files in *line 9* * The template used to generate the code in *line 10* (this can also be a path to a local copy of the template) * The enabled features of the template in *line 13*, here the `stubs` feature, which provides a simple implementation of interfaces. - Unreal Engine - C++14 - Qt6 - Python helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: ue_docs inputs: - helloworld.module.yaml output: ../ue_docs template: apigear-io/template-unreal@v3.2.2 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: cpp_hello_world inputs: - helloworld.module.yaml output: ../cpp_hello_world template: apigear-io/template-cpp14@v3.6.0 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: qt_hello_world inputs: - helloworld.module.yaml output: ../qt_hello_world template: apigear-io/template-qtcpp@v0.4.0 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: hello_world inputs: - helloworld.module.yaml output: ../py_hello_world template: apigear-io/template-python@v1.0.0 force: true features: - stubs ``` Targets You can extend this solution file with additional targets, each for a different technology with a different template. The `module.yaml` file is technology-independent and can be used with any template. Template Version We highly recommend specifying the exact version of the template. Otherwise, a newer version will be automatically downloaded and used for code generation. This can lead to unexpected behavior if file structures or code changes in the template. note Set the `force` parameter to `true` if you want to always override all generated files. When set to `false`, some files, such as implementations (stub feature), won't be updated. API files are always updated regardless of this setting. ### API Module File[​](#api-module-file "Direct link to API Module File") Use your preferred text editor to create the `helloworld.module.yaml` file with the following example content: helloworld.module.yaml ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` ## 4. Generate Code[​](#4-generate-code "Direct link to 4. Generate Code") ### Generate via CLI[​](#generate-via-cli "Direct link to Generate via CLI") The following snippet demonstrates how to run the CLI and provides an example output: ``` $ apigear generate solution apigear/helloworld.solution.yaml 10:52:20 INF generated 21 files in 30ms. (20 write, 0 skip, 1 copy) topic=gen ``` * The `generate` command instructs the CLI to generate code * The `solution` parameter specifies that we want to process a solution file ### Generate via Studio[​](#generate-via-studio "Direct link to Generate via Studio") 1. Open the project 2. Navigate to the `Solutions` tab 3. Click `Run` on the `helloworld.solution.yaml` entry ![Generating code](/img/apigear-studio-generate-code.png) Figure: Generating code ## 5. Use the generated cpp project[​](#5-use-the-generated-cpp-project "Direct link to 5. Use the generated cpp project") ### Project folder structure[​](#project-folder-structure "Direct link to Project folder structure") With the output directory set as in the example, both *ApiGear* files reside in an `apigear` subfolder next to the *cpp* generated files. In this case the folder structure should look similar to this ``` 📂hello-world ┣ 📂apigear ┃ ┣ 📜helloworld.solution.yaml ┃ ┗ 📜helloworld.module.yaml ┣ 📂cpp_hello_world ┃ ┣ 📂modules ┃ ┃ ┗ 📂io_world ┃ ┃ ┃ ┣ 📂generated ┃ ┃ ┃ ┣ 📂implementation ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┣ 📜hello.cpp ┃ ┃ ┃ ┃ ┣ 📜hello.h ┃ ┃ ┃ ┃ ┗ 📜hello.test.cpp ┃ ┗ 📜CMakeLists.txt ``` Using the solution file from the previous paragraph the code will be generated in the `cpp_hello_world` folder. With subfolder for each module, here `io_world` as the name of module (defined in line 2 of `helloworld.module.yaml`). It contains both features generated: a basic api and a stub implementation. The generated code provides cpp *C++* implementations. The following paragraphs show how you can use it. You can start your project loading the top level CMakeLists.txt in `cpp_hello_world` folder. The 'io\_world/io\_world/generated/api/' folder contains all definitions of the enums and structs for your module, as well as the interface classes for your Interfaces. From now on you can simply include the header files for the api interface or the stub implementation and use it. For more details on generated features please check [api](/template-cpp14/docs/features/api.md), [stubs](/template-cpp14/docs/features/stubs.md). tip Check our "examples" with all features enabled to get more working examples. note For the simulation check [the olink feature](/template-cpp14/docs/features/olink.md) which provides middle layer on your code side and the [simulation](/docs/scripting/backends/intro.md) explained. ### Create and run an example[​](#create-and-run-an-example "Direct link to Create and run an example") Prepare an `examples` folder in the `hello-world/cpp_hello_world` directory with a `main.cpp` like this: ``` #include "io_world/implementation/hello.h" #include int main(){ HelloWorldExample::IoWorld::Hello myHelloInstance; // Try out properties: subscribe for changes myHelloInstance._getPublisher().subscribeToLastChanged( [](const HelloWorldExample::IoWorld::Message& last) { std::cout << "last property changed "; }); // and ask for change. HelloWorldExample::IoWorld::Message messageForProperty; messageForProperty.m_content "New message"; myHelloInstance.setLast(messageForProperty); // Check the signals with subscribing for its change myHelloInstance._getPublisher().subscribeToJustSaid( [](const HelloWorldExample::IoWorld::Message& msg) { qDebug() << "justSaid signal emitted "; }); // and emit one. HelloWorldExample::IoWorld::Message messageForSignal; messageForSignal.m_content "Message from signal"; emit myHelloInstance.justSaid(messageForSignal); // Play around executing operations, maybe they emit signals? or change the properties? auto method_result = myHelloInstance.say(HelloWorldExample::IoWorld::Message(), HelloWorldExample::IoWorld::When::Now); return 0; } ``` add a `CMakeLists.txt` to the `examples` folder: ``` cmake_minimum_required(VERSION 3.1) project(MyExample) # append local binary directory for conan packages to be found list(APPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(MyExample main.cpp) # ensure maximum compiler support if(NOT MSVC) target_compile_options(app PRIVATE -Wall -Wextra -Wpedantic -Werror -fvisibility=hidden) else() target_compile_options(app PRIVATE /W4 /WX /wd4251) endif() find_package(io_world QUIET COMPONENTS io_world-core io_world-implementation ) target_link_libraries(app io_world::io_world-core io_world::io_world-implementation ) install(TARGETS app RUNTIME DESTINATION bin COMPONENT Runtime) ``` Add the `examples` subdirectory to the `hello-world/cpp_hello_world/CMakeLists.txt`. Now you can build the application and the libraries with it and run your example. tip If generating all features, you'll need Poco library (websockets library used by OLink feature) or Paho (Mqtt library used by mqtt feature)'. You then may want to use [conan](/template-cpp14/docs/features/conan.md) feature, which will get all necessary dependencies. Otherwise you might want to add `set(CMAKE_MODULE_PATH "path/to/your/poco_or_paho_build")` to your `CMakeLists` or use option `-DCMAKE_PREFIX_PATH=$GITHUB_WORKSPACE/path/to/poco_or_paho_build` . You might want also use conan for first time - to get you all those dependencies and then use paths provided by conan. --- # Features This guide explains how to use the generated code, what are the available features and their benefits. info A feature is a part of the template that generates a specific aspect of the code. For example, the `api` feature generates the core API interfaces and the `stubs` feature generates a stub implementation for the API. ## Get started[​](#get-started "Direct link to Get started") This template generates code for pure c++ projects. In order to successfully compile and use the code, you need to have a working c++ compiler toolchain installed. note Basic c++ knowledge is necessary. ### Code generation[​](#code-generation "Direct link to Code generation") Follow the documentation for the [code generation](/docs/guide/quick-start.md) in general and [CLI](/docs/cli/generate.md) or the [Studio](/docs/studio/intro.md) tools. Or try first the [quick start guide](/template-cpp17/docs/quickstart.md) which shows how to prepare api and generate code out of it. tip For questions regarding the template please go to our [discussions page](https://github.com/orgs/apigear-io/discussions). For feature requests or bug reports please use our [issue tracker](https://github.com/apigear-io/template-cpp17/issues). ### Example API[​](#example-api "Direct link to Example API") The following code snippet contains the *API* definition which is used throughout this guide to demonstrate the generated code and its usage. Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` ## Features[​](#features-1 "Direct link to Features") ### Core Features[​](#core-features "Direct link to Core Features") Features generate a view model for the *API* definition. This can be used to implement a working *C++* service and directly use it in your project. * [api](/template-cpp17/docs/features/api.md) - generates compilable base pure interfaces for your *API*, as also a basic implementation for data types and subscriber/publisher as abstract classes that describe the notification system for users of your interfaces. * [stubs](/template-cpp17/docs/features/stubs.md) - adds a basic stub implementation for the *API*, you'll get classes that can actually be instantiated with a ready to use publishers, this is a good starting point for your implementation. * [conan](/template-cpp17/docs/features/conan.md) - build and packaging support for your project. ### Extended Features[​](#extended-features "Direct link to Extended Features") Features can be used in combination with *API* and add more functionality on top, like simulation support (see [olink](/template-cpp17/docs/features/olink.md#simulation)) * [olink](/template-cpp17/docs/features/olink.md) - provides a client and server adapters for each interface, that can be connected to any of the other technology templates with support for [ObjectLink](/docs/protocols/objectlink/intro.md). Use this feature to connect with ApiGear simulation tools. * examples\_olink - examples of generated code for the olink feature. Contains: * `olinkserver` shows use of your interfaces as an olink services. * `olinkclient` shows use of your interfaces as an olink clients. * [monitor](/template-cpp17/docs/features/monitor.md) - generates a middle-ware layer which logs all API events to the [CLI](/docs/cli/intro.md) or the [Studio](/docs/studio/intro.md) * [MQTT](/template-cpp17/docs/features/mqtt.md) *experimental* - provides minimal working adapters for MQTT client and service side for each interfaces. Check also MQTT in other technology templates that supports it. * examples\_mqtt - examples of generated code for the olink feature. Contains: * `mqttserver` shows use of your interfaces with mqtt adapted for your services. * `mqttclient` shows use of your interfaces as a mqtt adapted for your services users. * [NATS](/template-cpp17/docs/features/nats.md) *experimental* - provides minimal working adapters for NATS client and service side for each interfaces. Check also NATS in other technology templates that supports it. * * examples\_nats - examples of generated code for the olink feature. Contains: * `natsserver` shows use of your interfaces with nats adapted for your services. * `natsclient` shows use of your interfaces as a nats adapted for your services users. * examples of generated code for basic features: * `app` simple example with stubs. * `appthreadsafe` simple example with stubs wrapped with thread safe decorator. See [thread safe decorator from core features](/template-cpp17/docs/features/stubs.md#core) ![Overview of features for user application, including receiving data from network: Bottom floor shows possible inputs for your API, you can either obtain data from the network with OLink or MQTT or use local (thread safe if necessary) implementation.](/img/cpp17/features/featuresApp.png) Figure: Overview of features for user application, including receiving data from network: Bottom floor shows possible inputs for your API, you can either obtain data from the network with OLink or MQTT or use local (thread safe if necessary) implementation. note Theoretically you can use the Thread Safe Decorator with other features that implements API: OLinkClient and MQTT Client, but they, already provide thread safety on at least the same level as the Thread Safe Decorator. ![Overview of features for user application, including publishing data through network: Topmost floor shows your options for using your local implementation (bottom floor): you can use it in your local app and/or use method of sharing the data with clients in the network. Consider then using thread safe version of your implementation.](/img/cpp17/features/featuresServer.png) Figure: Overview of features for user application, including publishing data through network: Topmost floor shows your options for using your local implementation (bottom floor): you can use it in your local app and/or use method of sharing the data with clients in the network. Consider then using thread safe version of your implementation. There are also an *internal* features: * `apigear`, which is generated for the *extended* features and is explained with them. * `core`, which is generated for the stubs and the *extended* features. For the explanation see [core documentation](/template-cpp17/docs/features/stubs.md#core) Each feature can be selected using the solution file or via command line tool. note \_Features are case sensitive, make sure to always **use lower-case.** \_ tip The *meta* feature `all` enables all specified features of the template. If you want to see the full extent of the generated code `all` is easiest solution. Please note, `all` is part of the code generator and not explicitly used within templates. ## Folder structure[​](#folder-structure "Direct link to Folder structure") This graph shows the full folder structure which is generated for `all` features enabled, but skips the [conan](/template-cpp17/docs/features/conan.md) files. Generated features are encapsulated in separate folders for each module or for the common features like `examples` and the internal helper feature `apigear`, a level above, in the `generation target` level, here `cpp_hello_world`. For more details visit the documentation for each feature. ``` 📂hello-world ┣ 📂apigear ┃ ┣ 📜helloworld.solution.yaml ┃ ┗ 📜helloworld.module.yaml ┣ 📂cpp_hello_world ┃ ┣ 📂apigear ┃ ┣ 📂examples ┃ ┣ 📂modules ┃ ┃ ┗ 📂io_world ┃ ┃ ┃ ┣ 📂conan ┃ ┃ ┃ ┣ 📂generated ┃ ┃ ┃ ┃ ┣ 📂api ┃ ┃ ┃ ┃ ┣ 📂core ┃ ┃ ┃ ┃ ┣ 📂monitor ┃ ┃ ┃ ┃ ┣ 📂mqtt ┃ ┃ ┃ ┃ ┗ 📂olink ┃ ┃ ┃ ┣ 📂implementation ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┗ 📜Io_worldConfig.cmake.in ┃ ┗ 📜CMakeLists.txt ``` --- # API Feature The feature `api` is the bare minimum for code generation. The feature will generate: * core behavioral API for the defined *interfaces* * interfaces for [publisher](/template-cpp17/docs/features/api.md#publisher) and [subscriber](/template-cpp17/docs/features/api.md#subscriber) tailored for each interface * Enumerations for the defined *enums* * Data structured with a core implementation for the defined *structs* note Check out the [stubs](/template-cpp17/docs/features/stubs.md) feature which provides fully functional implementation for the publisher. ### Files overview per module[​](#files-overview-per-module "Direct link to Files overview per module") Using the example API definition Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure will be generated inside the target folder. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┣ 📂cpp_hello_world ┃ ┣ 📂apigear ┃ ┣ 📂examples ┃ ┣ 📂modules ┃ ┃ ┗ 📂io_world ┃ ┃ ┃ ┣ 📂generated ┃ ┃ ┃ ┃ ┣ 📂api ┃ ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┃ ┣ 📜common.h ┃ ┃ ┃ ┃ ┃ ┣ 📜datastructs.api.cpp ┃ ┃ ┃ ┃ ┃ ┣ 📜datastructs.api.h ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.api.h ┃ ┃ ┃ ┃ ┃ ┗ 📜io_world.h .. . ``` ### Enums and Structures[​](#enums-and-structures "Direct link to Enums and Structures") Files `datastructs.api.h` and `datastructs.api.cpp` contain core data structures and enumerations used in your module. #### Enums[​](#enums "Direct link to Enums") Inside the `datastructs.api.h` you will find all the enums defined in your API. For each enum there is a `enum class` generated with all the values defined. For convenience of use each enum has a `toEnum` function, which converts given `uint8_t` to enum value. #### Structs[​](#structs "Direct link to Structs") All structs defined in your API are generated in `datastructs.api.h` and `datastructs.api.cpp` files. They are used to communicate data between your API and its users. For convenience of use each structure has: * equality comparison operators * empty constructor and a constructor with parameters for all its fields. ### Interfaces[​](#interfaces "Direct link to Interfaces") For each interface inside an AP module there is separate file generated, like the `📜hello.api.h`. The interface class contains pure virtual functions as well as pure virtual subscriber and publisher classes. The interface has: * pure virtual `getters` and a `setters` for each property ``` virtual void setLast(const Message& last) = 0; virtual const Message& getLast() const = 0; ``` * pure virtual functions for each operation ``` virtual int say(const Message& msg, When::WhenEnum when) = 0; ``` * and their asynchronous versions ``` virtual std::future sayAsync(const Message& msg, WhenEnum when) = 0; ``` * an access to a publisher The interface does not contain signals defined in your interface, the signals feature is achieved with a subscriber/publisher mechanism. A class interested in notification on signal emission (and property changes) needs to use the publisher to subscribe for those events. The important thing is that your implementation of the interface should notify the `IHelloPublisher` about emitted signals or state changed. note Notice the convention for the getter - we use an underscore `_` - this function is added by the generator to your interface. The underscore is used to prevent name collision with your implementation of the interface. ``` virtual IHelloPublisher& _getPublisher() const = 0; ``` #### Publisher[​](#publisher "Direct link to Publisher") The `IHelloPublisher` provides the interface for subscribing and unsubscribing from a signal emission or a property change. Use of such a class is mandatory for implementation and allows users of your API to react on changes. The publisher is responsible for keeping its clients informed about requested changes. But it is the interface (here `IHello`) implementation responsibility to call the publish functions from its API when signal or property change occurs e.g: ``` void Hello::setLast(const Message& last) { ... // call the publish function m_publisher->publishLastChanged(last); } ``` There are two ways of subscribing for changes. Either provide an implementation for `ISubscriber` interface class and use is with those publisher's functions ``` void subscribeToAllChanges(IHelloSubscriber& subscriber); void unsubscribeFromAllChanges(IHelloSubscriber& subscriber); ``` The subscribe ISubscriber assures your subscriber will be informed about all emitted signals and all property changes. The other option is to use the *parallel* system of notification which doesn't require the `ISubscriber` and informs your Interface user about change of specific property or signal emission. E.g for `LastChanged` property we have: ``` long subscribeToLastChanged(HelloLastPropertyCb callback); // returns handleId that needs to be used to unsubscribe void unsubscribeFromLastChanged(long handleId); ``` The publisher will execute the provided callback function each time and only when this property is changed. caution For both methods of subscribing make sure to remove subscription for a ISubscriber/callback before it becomes invalid. Also make sure that the subscribed function is not blocking and returns immediately! The publish functions need to be called by the implementation of the `IHello` on each property change or signal emission to notify all the subscribers about this change. note Subscriptions systems are parallel - if you use both of them for single interface user i.e subscribe your class as an `ISubscriber` and for a single change your subscriber will be informed twice about that change, one for each subscription mechanism. This means that the unsubscribe functions for both mechanisms work only for that mechanism, i.e. the unsubscribe with `ISubscriber` will not remove any subscriptions for the specific property/signal and they stay intact. #### Subscriber[​](#subscriber "Direct link to Subscriber") For each interface we generate the `ISubscriber`, for example here the `IHelloSubscriber` which contains a set of functions for all the properties and signals for the notification mechanism. The implementation for `IHello` contains a publisher which can be used to subscribe for those changes called `IHelloSubscriber`. Each time publisher will receive publish request it will notify all subscribed `IHelloSubscriber` object about change. You can use `IHelloSubscriber` class to implement your local clients of the `IHello` or a network service adapter - see [Olink Server](/template-cpp17/docs/features/olink.md#olink-server-adapter). Below is a small code snippet which shows an example implementation for `IHelloSubscriber`, a class which main functionality is reacting on changes in `Hello`. ``` class HelloUser : public IHelloSubscriber { public: HelloUser(IHello& Hello) : m_Hello(Hello) { m_Hello._getPublisher().subscribeToAllChanges(*this); } ~HelloUser() { m_Hello._getPublisher().unsubscribeFromAllChanges(*this); } void onJustSaid(const Message& /*msg*/) override { // do something with msg. } void onLastChanged(const Message& /*last*/) override { // do something with last; } // Some other functionality. private: IHello& m_Hello } ``` ### Other[​](#other "Direct link to Other") `📜 CMakeLists.txt` for building this module, describing it's dependencies, and exposing built package for other features that will use it.
`📜 common.h` prepares dll import/export statements. --- # Conan package manager With this feature you we generate a conan support for your project. It helps with packaging and to managing dependencies for different platforms, OSs, compilers, architecture, cross-builds. Read more about conan and its all benefits [here](https://conan.io/). tip Please make sure your C++ standard is set to 17 in your conan profile i.e.:
`compiler.cppstd=17` ### File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With an example API Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` The files for conan will be generated for project: ``` 📂hello-world ┣ 📂apigear ┣ 📂cpp_hello_world ┃ ┣ 📂apigear ┃ ┃ ... ┃ ┃ ┗ 📜conanfile.py ┃ ┣ 📂examples ┃ ┃ ┣ 📂 app ┃ ┃ ┃ ... ┃ ┃ ┃ ┗ 📜conanfile.txt ┃ ┃ ... ┃ ┣ 📂modules ┃ ┃ ┗ 📂io_world ┃ ┃ ┃ ┣ 📂conan ┃ ┃ ┃ ┃ ┣ 📂test_package ┃ ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┃ ┣ 📜conanfile.py ┃ ┃ ┃ ┃ ┃ ┗ 📜main.cpp ┃ ┃ ┃ ┃ ┗ 📜conanfile.py ┣ 📂scripts ┃ ┣ 📜test_conan.bat ┃ ┣ 📜test_conan.sh ``` ### Run scripts[​](#run-scripts "Direct link to Run scripts") The top level files you'll find in '📂hello-world/cpp\_hello\_world' `📜test_conan.bat` (for windows) and `📜test_conan.sh` (for linux based systems) contain set of command line commands to build whole project. The scripts build the project twice: * Firstly, using a package development commands. This way is recommended for local development, testing and modifications/customization of the build process in the user space without recompiling the entire package. Used command is `conan build ..` (preceded by `source` command that copies necessary sources and `install` command which installs the requirements and generates the info files). You can read more about build [here](https://docs.conan.io/1/reference/commands/development/build.html). * Secondly, with one of conan creator commands `conan create ...`. It is primarily used to generate, package, and upload packages to a repository. It calls the local conanfile.py ‘package()’ method. The `conanfile.py` is located in the `📂modules` and created for each module separately. Creator Commands are more geared toward recipe creation, versioning, and repository interactions. You can read more about package [here](https://docs.conan.io/1/reference/commands/development/package.html). We use CMake build system with conan. tip Modify the script to suit your needs. You can choose one of the building methods depending on current stage of your package development lifecycle. Some of the above files, the `📂cpp_hello_world/apigear/conanfile.py` or `conanfiles.txt` generated for each example were present even without conan feature - for simplicity of generation rules.
The `📂cpp_hello_world/apigear/conanfile.py` contains recipe for building additional library - network layer for cpp-template, independent from your API, and necessary for [ObjectLink core](https://github.com/apigear-io/objectlink-core-cpp), for aligning to MQTT protocol or for tracing.
The `📂cpp_hello_world📂modules/io_world/conan/conanfile.py` is the main recipe for your API module, which contains all the info necessary for packaging. Dependencies for the examples are stored in conanfile.txt files in each of the examples ( conanfile.txt is a simplification of conanfile.py, that can be used exclusively to consume dependencies, but not to create packages). The test package Files inside the `📂cpp_hello_world/modules/io_world/conan/test_package contain a test package`. It instantiates `Hello` implementation from a `stub` feature. It is a check that package for `io_world` was created in proper way, can be found and used. --- # Monitor Use the monitor feature to examine the interface calls, state and signals during runtime. With this feature you obtain a monitor client and a monitored version of your interfaces. The monitoring server is embedded into the [ApiGear Studio](/docs/studio/intro.md) and [CLI version](/docs/cli/intro.md). More details on [monitoring](/docs/monitor/intro.md) ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With our example API definition Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure will be generated. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┃ ... ┣ 📂cpp_hello_world ┃ ┣ 📂apigear ┃ ┃ ... ┃ ┃ ┣ 📂tracer ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┣ 📜tracer.cpp ┃ ┃ ┃ ┣ 📜tracer.h ┃ ┃ ┃ ┗ 📜tracer.test.cpp ┃ ┣ 📂examples ┃ ┣ 📂modules ┃ ┃ ┗ 📂io_world ┃ ┃ ┃ ┣ 📂generated ┃ ┃ ┃ ┃ ┣ 📂monitor ┃ ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.tracedecorator.cpp ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.tracedecorator.h ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.tracer.cpp ┃ ┃ ┃ ┃ ┃ ┗ 📜hello.tracer.h ... ``` ### Interface Independent Trace Client[​](#interface-independent-trace-client "Direct link to Interface Independent Trace Client") When generating the monitor feature (or olink feature) you'll get an additional folder in the top most directory:`📂hello-world/apigear`. The `📂tracer` subfolder contains a client for logging, with a network layer (based on the [POCO](https://docs.pocoproject.org/current/Poco.Net.html) library). The class connects to the `Apigear Trace Server` and takes care of formatting and sending the traces. ### Monitor[​](#monitor-1 "Direct link to Monitor") Files `📜hello.tracedecorator.h` and `📜hello.tracedecorator.cpp` contain the trace wrapper for the `Hello` interface. The wrapper will be generated for each interface inside the API definition. ``` class HELLO_WORLD_EXAMPLE_IO_WORLD_EXPORT HelloTraceDecorator : public IHello, public IHelloSubscriber { explicit HelloTraceDecorator(IHello& impl, ApiGear::PocoImpl::Tracer& tracer) ... }; ``` The tracer uses the implementation of `IHello` - wraps each interface operation call and the notifications of property changed and signal emitted with a lgo to a monitor tool. The other calls are just forwarded to your implementation - so you can use the `HelloTraceDecorator` as a `Hello` object. note The wrapped object `IHello& impl` is just the object that fulfills the `IHello` interface, so the implementation can be e.g. the [`OlinkClient`](/template-cpp17/docs/features/olink.md) version of `Hello`. The `📜hello.tracer.h` and `📜hello.tracer.cpp` files contain helper to prepare interface specific trace data for the general tracing [TraceClient](/template-cpp17/docs/features/monitor.md#tracing-example) ### Tracing example[​](#tracing-example "Direct link to Tracing example") Prepare an application (generate for both examples and monitor features) that uses the tracer: ``` int main(){ ApiGear::PocoImpl::Tracer tracer; tracer.connect("http://localhost:8182", "testExampleApp"); std::unique_ptr helloImplementation = std::make_unique(); std::unique_ptr tracedHello = IoWorld::HelloTraceDecorator::connect(*helloImplementation, tracer); // use your tracedHello as it was Hello implementation, all property changes, and signals and method execution and function calls will be traced. auto lastMessage = tracedHello->getLast(); tracedHello->say(lastMessage, IoWorld::WhenEnum::Soon); IoWorld::Message someMessage("the new content"); tracedHello->setLast(someMessage); tracedHello->_getPublisher().publishJustSaid(someMessage); return 0; } ``` Make sure that you are using the same port for sending and receiving traces. For sending: check yor setting for `ApiGear::PocoImpl::Tracer`. For receiver: go to the ApiGear Studio settings and set correct port number. By default the address is set to `ws://localhost:8182/ws`. --- # MQTT caution This is an experimental feature. It contains the smallest working set of functionalities to adapt the generated interface for use with the MQTT protocol. It doesn't include any security, the error handling is minimal and it is not production ready. Please also check issues on github for this template. note A [PAHO](https://eclipse.dev/paho/index.php?page=clients/python/index.php) library is used to implement MQTT in this library. It seems to have significant performance limitations for high load. If you need to use MQTT in high load application consider using our qt template - currently seems to be working much better. This feature does not only introduce MQTT protocol into your project, but also show that an existing protocol can be adapted for sharing your data in your ecosystem. When going through this document you may notice this implementation contains general client/server adapters in `📂hello-world/apigear/mqtt` and an interface specific part generated from templates for each interface in `📂hello-world/cpp_hello_world/modules/io_world/generated/mqtt`. This feature provides a *client* and *service* adapter for your interfaces for the MQTT protocol. It allows you to connect different applications in the same or different technologies (check all of our [templates](/docs/sdk/intro.md)). * Use an *Mqtt client* instead of your interface implementation to be able to receive data from remote service. * Use an *Mqtt server adapter* to expose your interface implementation as a remote service. tip The MQTT broker is not provided with the template. To be able to run client and service you need to run a broker of your choice, which is accessible for both client and service. caution In this library we use STL classes in public interfaces. We assume that this library is built from source (configuration provided by generated `CMakeLists`). Otherwise, it has to be ensured that your build configuration matches the library build configuration. ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With our API definition Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure is generated. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┃ ... ┣ 📂cpp_hello_world ┃ ┣ 📂apigear ┃ ┃ ┣ 📂mqtt ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┣ 📜mqttclient.cpp ┃ ┃ ┃ ┣ 📜mqttclient.h ┃ ┃ ┃ ┣ 📜mqttservice.cpp ┃ ┃ ┃ ┣ 📜mqttservice.h ┃ ┃ ┃ ┣ ... (helper files) ┃ ┃ ... ┃ ┣ 📂examples ┃ ┣ 📂modules ┃ ┃ ┗ 📂io_world ┃ ┃ ┃ ┣ 📂generated ┃ ┃ ┃ ┃ ┣ 📂mqtt ┃ ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┃ ┣ 📜helloclient.cpp ┃ ┃ ┃ ┃ ┃ ┣ 📜helloclient.h ┃ ┃ ┃ ┃ ┃ ┣ 📜helloservice.cpp ┃ ┃ ┃ ┃ ┃ ┗ 📜helloservice.h ... ``` ### ApiGear MQTT - The Network Layer[​](#apigear-mqtt---the-network-layer "Direct link to ApiGear MQTT - The Network Layer") When using the `mqtt` feature (or any of those: `olink`, `monitor` feature) you'll get an additional folder at the top most directory: the `📂hello-world/cpp_hello_world/apigear`. The `📂mqtt` subfolder contains objects that implement a network layer (based on [PAHO](https://eclipse.dev/paho/index.php?page=clients/python/index.php) library) for the MQTT protocol. Those are: * `Client` - Adapts the MQTT client, to serve as an network endpoint for [interface client adapters](/template-cpp17/docs/features/mqtt.md#mqtt-client-adapter). Exposes: * methods that allow receiving data from remote service: subscribing for properties changes, signals emission and method response invocation; * methods that allow remote using the service: requesting property change or invoking a method. The client may serve many client interface adapters, even for the same interfaces (allows subscribing for same topic). In case many interface client adapters for same interface are connected: property changes and signals are provided to all the interface client adapters, but the invoke method response will be delivered only for the one that requested it. * `ServiceAdapter` - Adapts the MQTT client to serve as an network endpoint for [interface service adapters](/template-cpp17/docs/features/mqtt.md#mqtt-server-adapter). Exposes: * methods that allow receiving requests from remote clients: subscribing for properties change requests, send method invocation; * methods that allow publishing property change, signal, functionality to handles sending a response for method invocation requests. This `ServiceAdapter` may be used for many interface service adapters, but it is not recommended to use more than one interface service adapter for same interface. tip Have in mind that MQTT might not be suitable for high-frequency messages especially with one mqtt client serving more than one object. Also the brokers have limits for messages number/size queued from one client. In case you are not getting all the messages consider changing those or splitting traffic between more clients (maybe some handle the properties, some handle the methods). ### MQTT Client Adapter[​](#mqtt-client-adapter "Direct link to MQTT Client Adapter") Files `📜helloclient.h` and `📜helloclient.cpp` implement the remote client for the `Hello` interface - a `HelloClient` class.
The object is an `IHello` implementation.
It requires an instance of Apigear::Mqtt::Client to work. It uses the Client to subscribe (and unsubscribe) for topics that allow receiving properties, signals and invoke responses from service. #### Properties[​](#properties "Direct link to Properties") The property getters (here `getLast()` ) return immediately the locally stored,last received value from service.
The property setter (here `void setLast(const Message& last)` ) requests setting a value on service side, local value is not changed.
You can subscribe to a property change event (here `last` property ) through the publisher which you can get from the API with `_getPublisher()`. Or you can subscribe as an `IHelloSubscriber` and get all changes. See [publisher](/template-cpp17/docs/features/api.md#publisher) and [subscriber](/template-cpp17/docs/features/api.md#subscriber) for more info on subscribing. When the client receives information that server changed the property, a target property (here `last`) is updated locally and a notifies subscribers that property has changed. note The connected interface client adapter has its local properties in sync with a service. The messages with property changes are retained in MQTT broker, so all properties which are already set are provided. #### Operations[​](#operations "Direct link to Operations") The operations have an async version, which is called by the immediate version. The async version sends an invoke operation request to a server. So calling `myHelloClientInstance.say(myWhen)` implements execution of `sayAsync` and waits for the result (for non-void type of operations). Have in mind that this is a blocking operation. #### Signals[​](#signals "Direct link to Signals") You should not emit any signals from a client. You can subscribe to any signals offered by your interface (here `justSaid)` signal), through the publisher. You can either select the signal you're interested in, or subscribe as an `IHelloSubscriber` to get all the signals and property change notifications. When a `HelloClient` client receives the message from server that indicates the signal was emitted it notifies all the subscribers that requested this notification.. #### Use `HelloClient`[​](#use-helloclient "Direct link to use-helloclient") `HelloClient` is an adapter of Mqtt (with protocol and network layer implementation), here provided by a `ApiGear::MQTT::Client`. All you need to do is to pass the `ApiGear::MQTT::Client` to your Interface Client Adapter, and request connecting to host when it is convenient for you. ``` // Create a client and make a connection auto mqttclient = std::make_shared("UniqueClientName"); // set up modules auto ioWorldHello = std::make_unique(mqttclient); // start mqtt connection mqttclient.connectToHost("tcp://localhost:1883"); // Use the same port number as your broker is using. Typically, without any other settings it is "1883". // use your ioWorldHello as it was Hello implementation ioWorldHello->say(IoWorld::WhenEnum::Soon); auto lastMessage = ioWorldHello->getLast(); IoWorld::Message someMessage("the new content"); ioWorldHello->setLast(someMessage); testIoWorldHello->_getPublisher().subscribeToJustSaid([](auto args) { /*handle the signal*/}); ``` ### MQTT Server Adapter[​](#mqtt-server-adapter "Direct link to MQTT Server Adapter") The files `📜helloservice.h` and `📜helloservice.cpp` contain the MQTT service side adapter for the `Hello` interface - the `HelloService` class. It uses the general MQTT service adapter `ApiGear::MQTT::Service`. `HelloService` object exposes the local object for remote usage with the MQTT protocol. It handles all the network requests, and calls on your local object. The client connections and communication with them is handled transparently for you, no additional actions are needed. #### Properties[​](#properties-1 "Direct link to Properties") Each time a property of your the `Hello` interface implementation changes, a message is sent to all connected clients with information, which property changed to which value. It is true for both - local calls on the `Hello` object, by other local `Hello` users or when a change property request message is received by this `HelloService` (the `HelloService` applies the property on your local `Hello` object and then the `HelloService` is triggered). #### Operations[​](#operations-1 "Direct link to Operations") The operations invocation which was triggered from the clients through the network will be performed on your local `Hello` object. The result of the operation (if any) will be returned only to the client, from which the message was send, not all clients. #### Signals[​](#signals-1 "Direct link to Signals") All the signals emitted by your local `Hello` objects are forwarded as an MQTT message to all connected clients. note Have in mind that the `Hello` implementation is not thread safe by default. #### Use `HelloService`[​](#use-helloservice "Direct link to use-helloservice") `HelloService` is an adapter of Mqtt (with protocol and network layer implementation), here provided by a `ApiGear::MQTT::ServiceAdapter` All you need to do is to pass the `ApiGear::MQTT::ServiceAdapter` and a local Hello implementation to your HelloService Adapter, then request connecting to host whenever convenient for you. ``` auto mqttservice = std::make_shared("ServiceUniqueNameInMqtt"); auto ioWorldHello = std::make_shared(); IoWorld::MQTT::HelloService mqttHelloService(ioWorldHello, mqttservice); // start mqtt connection mqttservice->connectToHost("tcp://localhost:1883"); // use your ioWorldHello as it was Hello implementation, all property changes, and signals will be passed to connected MqttHello clients. ioWorldHello->say(IoWorld::WhenEnum::Soon); IoWorld::Message someMessage("the new content"); auto lastMessage = ioWorldHello->getLast(); ioWorldHello->setLast(someMessage); // after this call - if new property is different than current one - all clients will be informed about new value. testIoWorldHello->_getPublisher().publishJustSaid(someMessage); ``` ### MQTT Messages[​](#mqtt-messages "Direct link to MQTT Messages") For the MQTT topic structure and payload format, see **[ApiGear over MQTT](/docs/protocols/mqtt/mapping.md)**. --- # NATS caution This is an experimental feature. It contains the smallest working set of functionalities to adapt the generated interface for use with the NATS protocol. It doesn't include any security, the error handling is minimal and it is not production ready. It doesn't use the JetStream nor the Streaming features. Please also check issues on github for this template. note The [CNATS](https://nats-io.github.io/nats.c/) library is used to implement NATS in this library. This feature provides NATS protocol adapters for your objects defined by interfaces. With this feature you can also see how an existing protocol can be adapted for sharing your data in your ecosystem. When going through this document you may notice this implementation contains general client/server adapters in `📂hello-world/apigear/nats` and an interface specific part generated from templates for each interface in `📂hello-world/cpp_hello_world/modules/io_world/generated/nats`. This feature provides a *client* and *service* adapter for your interfaces for the NATS protocol. It allows you to connect different applications in the same or different technologies (check all of our [templates](/docs/sdk/intro.md)). * Use an *Nats client* instead of your interface implementation to be able to receive data from remote service. * Use an *Nats server adapter* to expose your interface implementation as a remote service. You can expose same service for different technologies (olink, mqtt, other adapted protocol). tip The NATS server is not provided with the template. To be able to run apigear client and service (both serve as NATS clients) you need to run a NATS server, which is accessible for both client and service. caution In this library we use STL classes in public interfaces. We assume that this library is built from source (configuration provided by generated `CMakeLists`). Otherwise, it has to be ensured that your build configuration matches the library build configuration. ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With our API definition Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure is generated. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┃ ... ┣ 📂cpp_hello_world ┃ ┣ 📂apigear ┃ ┃ ┣ 📂nats ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┣ 📜natsclient.cpp ┃ ┃ ┃ ┣ 📜natsclient.h ┃ ┃ ┃ ┣ 📜natsservice.cpp ┃ ┃ ┃ ┣ 📜natsservice.h ┃ ┃ ┃ ┣ ... (helper files) ┃ ┃ ... ┃ ┣ 📂examples ┃ ┣ 📂modules ┃ ┃ ┗ 📂io_world ┃ ┃ ┃ ┣ 📂generated ┃ ┃ ┃ ┃ ┣ 📂nats ┃ ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┃ ┣ 📜helloclient.cpp ┃ ┃ ┃ ┃ ┃ ┣ 📜helloclient.h ┃ ┃ ┃ ┃ ┃ ┣ 📜helloservice.cpp ┃ ┃ ┃ ┃ ┃ ┗ 📜helloservice.h ... ``` ### ApiGear NATS - The Network Layer[​](#apigear-nats---the-network-layer "Direct link to ApiGear NATS - The Network Layer") When using the `nats` feature (or any of those: `olink`, `monitor`, `mqtt` feature) you'll get an additional folder at the top most directory: the `📂hello-world/cpp_hello_world/apigear`. The `📂nats` subfolder contains objects that implement a network layer (based on [CNATS](https://nats-io.github.io/nats.c/) library) for the NATS protocol. Those are: * `Client` - Adapts the NATS client, to serve as an network endpoint for [interface client adapters](/template-cpp17/docs/features/nats.md#nats-client-adapter). * `Service` - Adapts the NATS client to serve as an network endpoint for [interface service adapters](/template-cpp17/docs/features/nats.md#nats-server-adapter). * `Base` - shared by the `Client` and `Service` base which provides an interface to CNATS functionality tailored to the ApiGear requirements. it allows requesting call (with returning a message), subscribing for topics and publishing them. It also allows subscription and conntection state monitoring. note The CNATS allows connection configuration, please check [nats options](https://nats-io.github.io/nats.c/group__opts_group.html) for more information. The most important options used in this implementation, that may influence the performance are `SetSendAsap` and `UseGlobalMessageDelivery`, both set to true. ### NATS Client Adapter[​](#nats-client-adapter "Direct link to NATS Client Adapter") Files `📜helloclient.h` and `📜helloclient.cpp` implement the remote client for the `Hello` interface - a `HelloClient` class.
The object is an `IHello` implementation.
It requires an instance of `ApiGear::Nats::Client` to work. It uses the Client to subscribe (and unsubscribe) for topics that allow receiving properties, signals and invoke responses from service. #### Properties[​](#properties "Direct link to Properties") The property getters (here `getLast()` ) return immediately the locally stored, last received value from service.
The property setter (here `void setLast(const Message& last)` ) requests setting a value on service side, local value is not changed.
You can subscribe to a property change event (here `last` property ) through the publisher which you can get from the API with `_getPublisher()`. Or you can subscribe as an `IHelloSubscriber` and get all changes. See [publisher](/template-cpp17/docs/features/api.md#publisher) and [subscriber](/template-cpp17/docs/features/api.md#subscriber) for more info on subscribing. When the client receives information that server changed the property, a target property (here `last`) is updated locally and a notifies subscribers that property has changed. #### Operations[​](#operations "Direct link to Operations") The operations have an async version, which is called by the immediate version. The async version sends an invoke operation request to a server. So calling `myHelloClientInstance.say(myWhen)` implements execution of `sayAsync` and waits for the result or for method finishes in case of void functions. Have in mind that this is a blocking operation. #### Signals[​](#signals "Direct link to Signals") You can subscribe to any signals offered by your interface (here `justSaid)` signal), through the publisher. You can either select the signal you're interested in, or subscribe as an `IHelloSubscriber` to get all the signals and property change notifications. When a `HelloClient` client receives the message from server that indicates the signal was emitted it notifies all the subscribers that requested this notification. You should not emit any signals from a client. #### Connectivity[​](#connectivity "Direct link to Connectivity") The `HelloClient` subscribes for two connectivity messages for the `HelloService`: The `HelloService` availability message and `HelloService` init response (dedicated for a specific client instance). The `HelloClient` sends an init request message: * When it receives the `HelloService` availability message (if the HelloService wasn’t available when the HelloClient started). * When it connects to a NATS server and subscribes to the required topics (if the HelloService was already available it will receive it and will be able to respond). For this message the `HelloService` will respond with its current state - the `HelloService` init response message. #### Use `HelloClient`[​](#use-helloclient "Direct link to use-helloclient") `HelloClient` is an adapter of NATS (with protocol and network layer implementation), here provided by a `ApiGear::Nats::Client`. All you need to do is to pass the `ApiGear::Nats::Client` to your Interface Client Adapter, and request connecting to host when it is convenient for you. ``` auto client = std::make_shared(); auto testIoWorldHello = IoWorld::Nats::HelloClient::create(client); // Try out properties: subscribe for changes testIoWorldHello->_getPublisher().subscribeToLastChanged([](auto value){ std::cout << " Last " << std::endl; }); // or ask for change, when objest is ready auto idSubProp = testIoWorldHello->_subscribeForIsReady( [testIoWorldHello](bool connected) { if (!connected) { return; } IoWorld::Message someMessage("the new content"); testIoWorldHello->setLast(someMessage); }); // Check the signals with subscribing for its change. Signal must be emitted from server side. testIoWorldHello->_getPublisher().subscribeToJustSaid([](const IoWorld::Message& msg){ std::cout << " JustSaid " << std::endl; }); // Play around executing your operations auto idSubOperation = testIoWorldHello->_subscribeForIsReady( [testIoWorldHello](bool connected) { if (!connected) { return; } IoWorld::Message someMessage("Some new content for Say"); auto method_result = testIoWorldHello->say(someMessage, IoWorld::WhenEnum::Soon); }); //connect client->connect("nats://localhost:4222"); ``` ### Nats Server Adapter[​](#nats-server-adapter "Direct link to Nats Server Adapter") The files `📜helloservice.h` and `📜helloservice.cpp` contain the NATS service side adapter for the `Hello` interface - the `HelloService` class. It uses the general Nats service adapter `ApiGear::Nats::Service`. `HelloService` object exposes the local object for remote usage with the Nats protocol. It handles all the network requests, and calls on your local object. The client connections and communication with them is handled transparently for you, no additional actions are needed. #### Properties[​](#properties-1 "Direct link to Properties") Each time a property of your the `Hello` interface implementation changes, a message is sent to all connected clients with information, which property changed to which value. It is true for both - local calls on the `Hello` object, by other local `Hello` users or when a change property request message is received by this `HelloService` (the `HelloService` applies the property on your local `Hello` object and then the `HelloService` is triggered). #### Operations[​](#operations-1 "Direct link to Operations") The operations invocation which was triggered from the clients through the network will be performed on your local `Hello` object. The result of the operation (if any) will be returned only to the client, from which the message was send, not all clients. #### Signals[​](#signals-1 "Direct link to Signals") All the signals emitted by your local `Hello` objects are forwarded as an NATS message to all connected clients. #### Connectivity[​](#connectivity-1 "Direct link to Connectivity") A `HelloService` subscribes for an init request message form a `HelloClient`. It responds for the init request message with its state, only to this client.
A `HelloService` sends a `HelloService` availability message as soon as it is ready (connected to an external NATS server and has all its topics subscribed). On this message all already connected clients will repeat the init request. note Have in mind that the `Hello` implementation is not thread safe by default. #### Use `HelloService`[​](#use-helloservice "Direct link to use-helloservice") `HelloService` is an adapter of Nats (with protocol and network layer implementation), here provided by a `ApiGear::Nats::Service` All you need to do is to pass the `ApiGear::Nats::Service` and a local Hello implementation to your HelloService Adapter, then request connecting to host whenever convenient for you. ``` auto service = std::make_shared(); // set up modules std::shared_ptr testIoWorldHello = std::make_shared(); auto testIoWorldHelloService = IoWorld::Nats::HelloService::create(testIoWorldHello, service); // The client property change request will trigger other changes auto idSubProp = testIoWorldHello->_getPublisher().subscribeToLastChanged( [testIoWorldHello](auto value) { IoWorld::Message someMessage("ServerContent"); testIoWorldHello->say(someMessage, IoWorld::WhenEnum::Never); auto lastMessage = testIoWorldHello->getLast(); testIoWorldHello->_getPublisher().publishJustSaid(lastMessage); }); service->connect("nats://localhost:4222"); ``` ### Nats Messages[​](#nats-messages "Direct link to Nats Messages") For the NATS subject structure and payload format, see **[ApiGear over NATS](/docs/protocols/nats/mapping.md)**. --- # OLink This feature provides a *client* and a *server* adapter for your interfaces for the [ObjectLink](/docs/protocols/objectlink/intro.md) protocol. It allows you to connect different applications using the same or different technologies (check all of our [templates](/docs/sdk/intro.md)). Use an *OLink client* instead of your interface implementation to connect to a remote service, or to a the [ApiGear simulation](/template-cpp17/docs/features/olink.md#simulation). Use an *OLink server adapter* to expose your interface implementation as a remote service. Below you'll find short introduction on `ApiGear ObjectLink` protocol. Before details on client and server, you'll also find piece of information on a network layer implementation for handling ObjectLink in `template-cpp`. note The `OLink client` and the `OLink server` objects are ready-to-use as they are, they don't' need any extra implementation. If you want quickly start working with them go to use sections of [client](/template-cpp17/docs/features/olink.md#olink-client-adapter), [server](/template-cpp17/docs/features/olink.md#olink-server-adapter). ### Apigear ObjectLink protocol and ObjectLink core library[​](#apigear-objectlink-protocol-and-objectlink-core-library "Direct link to Apigear ObjectLink protocol and ObjectLink core library") The [ObjectLink](/docs/protocols/objectlink/intro.md) protocol is a lightweight websocket based protocol for Objects described with an interface. It allows connecting a client object with a server object, and perform remote operations like: remote property change request (client) or notifications on property changed (server), inform about signal emission (server) and allows requesting a remote method call (client) and delivering a response to the caller (server). The Olink feature for your interface uses a library [ObjectLink core](https://github.com/apigear-io/objectlink-core-cpp), common for cpp based templates. The provided `CMakeLists` already contain all the dependencies, so you don't have to add it manually. The library provides an abstraction layer for handling the protocol, independent of the actual network stack. It encodes and decodes the messages and delivers them to corresponding objects identified by a registry. The setup is minimal: instantiate the registry (for client and/or server) and pass them to network layer objects in [cpp apigear library](/template-cpp17/docs/features/olink.md#cpp-apigear---the-network-layer) caution In this library we use STL classes in public interfaces. We assume that this library is built from source (configuration provided by generated CMakeLists). Otherwise, it has to be ensured that your build configuration matches the library build configuration. ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With out example API Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure will be generated. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┃ ... ┣ 📂cpp_hello_world ┃ ┣ 📂apigear ┃ ┃ ┣ 📂mqtt ┃ ┃ ┣ 📂olink ┃ ┃ ┃ ┣ 📂private ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┣ 📜olinkconnection.cpp ┃ ┃ ┃ ┣ 📜olinkconnection.h ┃ ┃ ┃ ┣ 📜olinkhost.cpp ┃ ┃ ┃ ┣ 📜olinkhost.h ┃ ┃ ┃ ┣ ... (helper files) ┃ ┃ ... ┃ ┣ 📂examples ┃ ┣ 📂modules ┃ ┃ ┗ 📂io_world ┃ ┃ ┃ ┣ 📂generated ┃ ┃ ┃ ┃ ┗ 📂olink ┃ ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┃ ┣ 📜helloclient.cpp ┃ ┃ ┃ ┃ ┃ ┣ 📜helloclient.h ┃ ┃ ┃ ┃ ┃ ┣ 📜helloservice.cpp ┃ ┃ ┃ ┃ ┃ ┗ 📜helloservice.h ┃ ┣ 📜 Lifecycle for olink client-server implementation.md ... ``` note You may notice an extra document: `📜Lifecycle for olink client-server implementation.md` which explains life cycle of main objects used when using an olink protocol, in a form of flow diagrams. ### cpp ApiGear - The Network Layer[​](#cpp-apigear---the-network-layer "Direct link to cpp ApiGear - The Network Layer") When generating the olink feature (or monitor feature) you'll get an additional folder at the top most directory: the `📂hello-world/cpp_hello_world/apigear`. The `📂olink` subfolder contains objects that implement a network layer (based on [Poco](https://docs.pocoproject.org/current/Poco.Net.html) library) for the ObjectLink protocol. Those are: * `OlinkClient` - the client, that serves your [interface client adapters](/template-cpp17/docs/features/olink.md#olink-client-adapter). The class is responsible for network connection for ObjectLink client side. It uses the ObjectLink core library for handling messages according to the ObjectLink protocol. Handles linking and unlinking with a remote service for the sink added by the user (the interface OlinkClient Adapters), with regard to the connection state. All the messages dedicated to your object will be delivered to the adapter and proper actions will be performed for the clients that use it, without any of your actions. Also the OlinkClient holds a message queue for messages during disconnected time. * `OlinkHost` and a helper class `OlinkRemote` that you'll find in `📂private` subfolder The server, which hosts your services exposed to network through the [interface server adapters](/template-cpp17/docs/features/olink.md#olink-server-adapter). The class is responsible to set up the server and manage connections requested by clients. It uses ObjectLink core library for handling messages according to the ObjectLink protocol. All incoming messages will be translated to calls and routed to your local object. ### Olink Client Adapter[​](#olink-client-adapter "Direct link to Olink Client Adapter") Files `📜helloclient.h` and `📜helloclient.cpp` contain the olink client version of the `Hello` interface - an `HelloClient` class. It implements two interfaces: `IHello` and a `IObjectSink`. IObjectSink (click to expand) ``` class IObjectSink { public: virtual std::string olinkObjectName() = 0; virtual void olinkOnSignal(const std::string& signalId, const nlohmann::json& args) = 0; virtual void olinkOnPropertyChanged(const std::string& propertyId, const nlohmann::json& value) = 0; virtual void olinkOnInit(const std::string& objectId, const nlohmann::json& props, IClientNode* node) = 0; virtual void olinkOnRelease() = 0; }; ``` The `IObjectSink` interface comes from [ObjectLink core](https://github.com/apigear-io/objectlink-core-cpp) and is necessary for handling incoming messages from the server side and is used by a `OLinkClient` ([cpp Apigear Olink lib](/template-cpp17/docs/features/olink.md#cpp-apigear---the-network-layer)). Apart from setting up and tear down of the `OLinkHello` object, you don't need to perform any additional actions, the object `OLinkHello` will communicate with the server transparently and you should use it as an `IHello` Object only. #### Properties[​](#properties "Direct link to Properties") The property getters (here `Message last()` ) return immediately the locally stored last received value from server. The property setter (here `void setLast(const Message& last)` ) requests setting a value on server side, local value is not changed. You can subscribe to a property changed (here `last` property ) through the publisher you can get from the `HelloClient` with `_getPublisher()`. Or you can subscribe (instead of the above) for as an IHelloSubscriber to receive all changes (and signals). When the client receives information that server changed the property, a target property (here `last`) is updated locally and a notifies subscribers that property has changed. note The connected client has its local properties initialized - on a successful linking client with server the client receives a message with current state of properties of the service on server side. #### Operations[​](#operations "Direct link to Operations") The operations have an additionally `async` version, which is called by the immediate version. The async version sends an invoke operation request to a server. So calling `myOlinkHelloInstance.say(myWhen)` implements execution of `sayAsync` and waits for the result (for non-void type of operations). Have in mind that this is a blocking operation. #### Signals[​](#signals "Direct link to Signals") You should not emit any signals from a client. You can connect to any signals offered by your interface (here `void justSaid(const Message& msg)` ), through the publisher. You can either select the signal you're interested in, or subscribe as an IHelloSubscriber to get all the signals and property change notifications. When a `HelloClient` client receives the message from server that indicates the signal was emitted it emits a signal (here `justSaid`). #### Use `HelloClient`[​](#use-helloclient "Direct link to use-helloclient") As mentioned earlier you need a network layer, here provided by a `ApiGear::ObjectLink::OLinkClient` which also contains the protocol handling `ClientNode`. All you need to do is give it a global `ApiGear::ObjectLink::ClientRegistry`, request connecting to host when it is convenient for you and then add your `HelloClient`. ``` ApiGear::ObjectLink::ClientRegistry registry; ApiGear::ObjectLink::OLinkClient client(registry); // Create a global registry. ApiGear::ObjectLink::ClientRegistry registry; // Create a client and make a connection ApiGear::PocoImpl::OlinkConnection client(registry); // Create your client and request linking, which will try to connect with a server side for this object. auto ioWorldHello = std::make_shared(); client.connectAndLinkObject(ioWorldHello); client.connectToHost(Poco::URI("ws://localhost:8182")); // You can try out properties auto lastMessage = ioWorldHello->getLast(); // Executing the methods ioWorldHello->say(lastMessage, IoWorld::WhenEnum::Soon); IoWorld::Message someMessage("the new content"); ioWorldHello->setLast(someMessage); // Or subscribe for signals. ioWorldHello->_getPublisher().subscribeToJustSaid([](auto args) { /*handle the signal*/}); // remember to unlink your object if you won't use it anymore. client.unlinkObjectSource(ioWorldHello->olinkObjectName()); ``` caution `ApiGear::ObjectLink::ClientRegistry` can have only one object with the same identifier. This means that, to have more than one `OlinkHello` objects you would need to put them in different registries. Each Object has an identifier which is created based on the Interface Name, this way client object can be matched with proper server object. ### Olink Server Adapter[​](#olink-server-adapter "Direct link to Olink Server Adapter") Files `📜helloservice.h` and `📜helloservice.h` contain the olink server adapter for the `Hello` interface - the `HelloService` class. It implements an `IObjectSource` interface (from [ObjectLink core](https://github.com/apigear-io/objectlink-core-cpp)), which wraps your `Hello` and exposes it for remote usage with the [ObjectLink](/docs/protocols/objectlink/intro.md) protocol. It handles all the network requests, and calls your local object. When creating the `HelloService` you need to provide the local `IHello` service object, you want to expose to clients. The `IObjectSource` interface: IObjectSource (click to expand) ``` class IObjectSource { public: virtual std::string olinkObjectName() = 0; virtual nlohmann::json olinkInvoke(const std::string& methodId, const nlohmann::json& args) = 0; virtual void olinkSetProperty(const std::string& propertyId, const nlohmann::json& value) = 0; virtual void olinkLinked(const std::string& objectId, IRemoteNode* node) = 0; virtual void olinkUnlinked(const std::string& objectId) = 0; virtual nlohmann::json olinkCollectProperties() = 0; }; ``` After setting up the OlinkHost (cpp Apigear Olink lib) and registering your Interface registered, the client connections and communication with them is handled transparently for you, no additional actions are needed. note All the received messages from the clients are handled in a thread in which the connection resides, this means that your object may be used from different threads (local and each connection). #### Properties[​](#properties-1 "Direct link to Properties") Each time a property change signal (here `void lastChanged(const Message& last)` ) is emitted, a message is sent to all connected clients. The message contains the information, which property changed and the corresponding value. This may occur either when you change a property directly on your local `Hello` object, or when a change property request message is received by the `HelloService`, which applies the property on your local `Hello` object. #### Operations[​](#operations-1 "Direct link to Operations") The remote operations invocation from the clients via the network will be performed on your local `Hello` object. The result of the operation (if any) will be returned only to the caller, not all clients. Have in mind that your object may be called from more that one thread. #### Signals[​](#signals-1 "Direct link to Signals") All the signals emitted by your local `Hello` objects are forwarded to all connected clients with an olink messages. #### Use `OLinkHelloAdapter`[​](#use-olinkhelloadapter "Direct link to use-olinkhelloadapter") As mentioned earlier you need a network layer, here provided by a `ApiGear::ObjectLink::OLinkHost` which also contains the protocol handling `RemoteNodes`. The `ApiGear::ObjectLink::OLinkRemote` objects are created automatically per connection. All you need to do is give a global `ApiGear::ObjectLink::RemoteRegistry` with your `OLinkHelloAdapter` in it to `ApiGear::ObjectLink::OLinkHost`. ``` ApiGear::ObjectLink::RemoteRegistry registry; auto logFunction = [](auto /*level*/, auto /*msg*/){ }; ApiGear::PocoImpl::OLinkHost testserver(registry,logFunction); auto ioWorldHello = std::make_shared(); auto ioWorldOlinkHelloService = std::make_shared(ioWorldHello, registry); registry.addSource(ioWorldOlinkHelloService); auto portNumber = 8000; // Start your server with your service added to registry. testserver.listen(portNumber); // use your ioWorldHello as it was Hello implementation, all property changes, and signals will be passed to connected OLink clients. auto lastMessage = ioWorldHello->getLast(); ioWorldHello->say(lastMessage, IoWorld::WhenEnum::Soon); IoWorld::Message someMessage("the new content"); ioWorldHello->setLast(someMessage); // after this call - if new property is different than current one - all clients will be informed about new value. testIoWorldHello->_getPublisher().publishJustSaid(someMessage); // remember to remove from registry if you won't use it anymore. registry.removeSource(ioWorldOlinkHelloService->olinkObjectName()); ``` ## Simulation[​](#simulation "Direct link to Simulation") The simulation can be used to test, demonstrate or develop applications without the need to have the actual service available. The simulation server is integrated into the [ApiGear studio](/docs/studio/intro.md) and the [CLI](/docs/cli/simulate.md). For simulating you will use [simulation scenarios](/docs/scripting/backends/scenario.md) They allow to define sequences of actions. The actions can change the property values of the service or emit signals. The scenarios can be written using a YAML schema. See more on [simulation](/docs/scripting/backends/intro.md). ### Prepare your application[​](#prepare-your-application "Direct link to Prepare your application") Make sure you are using OlinkClient as your `Hello` object, an instance on [cpp side](/template-cpp17/docs/features/olink.md#olink-client-adapter) You may try our example, it subscribes for signals and property changes, with a simple logging function. Create new folder under `examples` directory. Paste the below `main.cpp` and `CMakeLists.txt` files. Remember to add this directory to main `CMakeLists.txt` (or to extend test\_conan.bat/sh in case using conan). main.cpp (click to expand) ``` #include #include "io_world/generated/olink/helloclient.h" #include "apigear/olink/olinkconnection.h" #include "apigear/tracer/tracer.h" #include "apigear/olink/olinklogadapter.h" #include "olink/clientregistry.h" #include "olink/clientnode.h" #include "apigear/olink/olinkconnection.h" #include using namespace HelloWorldExample; int main() { // Create a global registry. ApiGear::ObjectLink::ClientRegistry registry; // Create a client and make a connection ApiGear::PocoImpl::OlinkConnection client(registry); // Create your client and request linking, which will try to connect with a server side for this object. auto ioWorldHello = std::make_shared(); client.connectAndLinkObject(ioWorldHello); client.connectToHost(Poco::URI("ws://localhost:8182")); // Or subscribe for signals. ioWorldHello->_getPublisher().subscribeToJustSaid([](auto& /*args*/) { std::cout << "Just said received" << std::endl; }); ioWorldHello->_getPublisher().subscribeToLastChanged([](auto& /*args*/) { std::cout << "Last Changed" << std::endl; }); bool keepRunning = true; std::string cmd; do { std::cout << "Enter command:" << std::endl; getline(std::cin, cmd); if (cmd == "quit") { keepRunning = false; client.disconnect(); } } while (keepRunning); client.disconnectAndUnlink(ioWorldHello->olinkObjectName()); return 0; } ``` CMAkeLists.txt (click to expand) ``` cmake_minimum_required(VERSION 3.1) project(OLinkClientSimuExample) # append local binary directory for conan packages to be found list(APPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set (SOURCES main.cpp ) add_executable(OLinkClientSimuExample ${SOURCES} ) # ensure maximum compiler support if(NOT MSVC) target_compile_options(OLinkClientSimuExample PRIVATE -Wall -Wextra -Wpedantic -Werror -fvisibility=hidden) else() target_compile_options(OLinkClientSimuExample PRIVATE /W4 /WX /wd4251) # disable the warning for getenv - needs better cross platform solution target_compile_definitions(OLinkClientSimuExample PRIVATE -D_CRT_SECURE_NO_WARNINGS) endif() find_package(io_world QUIET COMPONENTS io_world-core io_world-implementation io_world-monitor io_world-olink) target_link_libraries(OLinkClientSimuExample io_world::io_world-core io_world::io_world-implementation io_world::io_world-monitor io_world::io_world-olink ) install(TARGETS OLinkClientSimuExample RUNTIME DESTINATION bin COMPONENT Runtime) ``` note Make sure to configure the client to use the correct IP and port configuration of your simulation server e.g. set `client.connectToHost(QUrl("ws://127.0.0.1:8182/ws"));` and make sure your apigear simulation also uses this port. ### Prepare the scenario[​](#prepare-the-scenario "Direct link to Prepare the scenario") As a starting point you can check our simulation scenario: Scenario(click to expand) ``` schema: apigear.scenario/1.0 name: "first scenario" version: "1.0.0" #initial properties and setting gunction response interfaces: - name: io.world.Hello #( module io.world and interface Hello combination) properties: last: {content: "Initial"} operations: - name: say actions: - $return: { value: 88 } # sequence of changing properties and emitting signals sequences: - name: play with hello interval: 2000 # 2 seconds interface: io.world.Hello loops: 3 steps: # step is called every 2 secs according to interval - name: change property actions: - $set: { last: {content: "First Change of Property"} } - name: emit signal actions: - $signal: { justSaid: [ {content: "First Message"} ] } - name: change property AND emit signal actions: - $set: { last: {content: "Second Change of Property"} } - $signal: { justSaid: [ {content: "Other Signal"} ] } ``` Apart from the header with the scenario name and its version it consists of two parts: * initialization for each interface (here, our ui.world.Hello line 6), we're setting initial values of properties(line 8) and the return value of function calls (line 12) * sequence of events You can prepare more than one sequence. For each of them set interval between calling each action (line 16), set how many runs of that sequence you want to execute (line 18) and name the interface the action should be applied to (line 17). In each action you may change many properties, and emit signals. ### Run the simulation[​](#run-the-simulation "Direct link to Run the simulation") Put your scenario file in the `📂hello-world/apigear` along with other `.yaml` files. If you are using the studio, reload your project. Go to `settings` and make sure to set same the port as in your client app (here `8182`). Click the `play` button in the application in the `simulation` tab to start the simulation scenario, then start the client application. Instead of the ApiGear Studio application can also use its console version, run your scenario with command: ``` apigear simulate run path-from-cli-to-scenario/helloworldtest.scenario.yaml --addr :8182 ``` note We tried some measurements for the OLink performance. You may want to check the [our tests here](https://github.com/apigear-io/performance-checks). We're not providing any data of throughput and latency, as the branch is still in between alpha phase. In general we expect it to be fast as it has small overhead (which may be not sufficient for you). --- # Stubs The feature `stubs` adds: * a semi-featured starting point for your implementation and instantiable interfaces classes * an implementation for publishers * a setup for tests * a thread safe decorator for your interfaces With the stub feature you'll get an additional `core` folder generated, it contains common functionality used also by other features: Olink or Mqtt. Its content will be explained below. note For the basic skeleton implementation you'll find in `📂hello-world\cpp_hello_world\modules\io_world\implementation` the bear minimum classes are the [publisher](/template-cpp17/docs/features/stubs.md#publisher) and [data](/template-cpp17/docs/features/stubs.md#data) from `core` features. For the start focus on them and then explore more functionality. ### File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With our example API definition Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure will be generated. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┣ 📂cpp_hello_world ┃ ┣ 📂apigear ┃ ┣ 📂examples ┃ ┣ 📂modules ┃ ┃ ┗ 📂io_world ┃ ┃ ┃ ┣ 📂generated ┃ ┃ ┃ ┃ ┣ 📂api ┃ ┃ ┃ ┃ ┣ 📂core ┃ ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.data.h ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.publisher.cpp ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.publisher.h ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.threadsafedecorator.cpp ┃ ┃ ┃ ┃ ┃ ┣ 📜hello.threadsafedecorator.h ┃ ┃ ┃ ┃ ┃ ┣ 📜io_world.json.adapter.cpp ┃ ┃ ┃ ┃ ┃ ┣ 📜io_world.json.adapter.h ┃ ┃ ┃ ┃ ┃ ┗ 📜io_world.test.cpp ┃ ┃ ┃ ┣ 📂implementation ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┣ 📜hello.cpp ┃ ┃ ┃ ┃ ┣ 📜hello.h ┃ ┃ ┃ ┃ ┗ 📜hello.test.cpp ... ``` ### Implementation[​](#implementation "Direct link to Implementation") Files `📜hello.h` and `📜hello.cpp` contain the implementation of the `IHello`. The class skeleton: * adds the properties of the interface as a private class members - with a [HelloData structure](/template-cpp17/docs/features/stubs.md#data) * implements getters and setters for each property * owns a [publisher](/template-cpp17/docs/features/stubs.md#publisher) and shares it through the `_getPublisher` method implementation * provides empty implementation of operation for you to fill the business logic. tip When adding a logic don't forget to use the publisher each time you want the property change to be shared or the signal to be emitted. note We generate the import/export statements, here HELLO\_WORLD\_EXAMPLE\_IO\_WORLD\_EXPORT, for all the classes and structs that have an implementation in a *cpp* file and may be used outside of the library. ### Core[​](#core "Direct link to Core") #### Publisher[​](#publisher "Direct link to Publisher") Files `📜hello.publisher.cpp` and `📜hello.publisher.h` provide an implementation of a IHelloPublisher (api.md#Publisher). It stores clients of the Hello (either the `IHelloSubscriber`s or callbacks for specific change). When a call of the appropriate publish function is executed on the publisher instance, it informs all of interested clients about the change. This class is thread safe in a way that adding and removing subscribers (both for full interface or specific notifications) is thread safe. If you use publisher from many threads for notification you need to make sure that `ISubscriber` or the callbacks are thread safe or modify the publisher to use them in thread-safe way. You may also want to take a look at generated [IHello thread safe decorator](/template-cpp17/docs/features/stubs.md#thread-safe-decorator). #### Data[​](#data "Direct link to Data") The `📜hello.data.h` contains the helper structure for implementations of Hello. It stores all the properties that an interface has and initializes them to default values. #### Thread Safe Decorator[​](#thread-safe-decorator "Direct link to Thread Safe Decorator") Files `📜hello.threadsafedecorator.cpp` and `📜hello.threadsafedecorator.h` provide a wrapper for your `IHello` implementation which can be used to make property access thread safe. For each property it allows multiple get operations at the same time but only one set. The properties are guarded one by one - so at one time you can write to different properties. Operations are not guarded by default - the implementation may either be thread safe by design (const, re-entrant) or too complex to simply lock it. So depending on the logic in your methods you can decide to leave them without any mutexes, add same mechanism as for properties in the implementation file of the Hello interface, or try different solutions like introducing an event queue. #### Json adapters[​](#json-adapters "Direct link to Json adapters") Files `📜io_world.json.adapter.cpp` and `📜io_world.json.adapter.h` are generated per whole module. They prepare adapters for structs to easy convert to and from `nlohmann::json`. This is currently used by our implemented protocols for the data packing. With this implementation you use the structures as below ``` Message message = json.get(); nlohmann::json message = message; ``` ### Tests[​](#tests "Direct link to Tests") For each interface we provide a template for your tests: `📜hello.test.cpp`. We used [Catch2](https://github.com/catchorg/Catch2) for setting up a test skeleton. You can find there instantiating the interface, section with executing methods (no checks) and section with setting a property with check that it actually happen. That is a starting point for your tests. tip In case you'll need mocks, `Catch2` goes well along with [trompeloeil](https://github.com/rollbear/trompeloeil). --- # Template C++ 17 This is the documentation for the *cpp17* template for the [ApiGear](/docs/guide/quick-start.md) code generator. It is split in several parts: * [Quick-Start](/template-cpp17/docs/quickstart.md?current-template=template-cpp17) is the easiest way to get started * [Features](/template-cpp17/docs/features.md) explains the available code generator features and their usage and purpose. --- # Quick-Start The Quick-Start guide explains how in a few steps you get from an API definition to a functional c++ example. Steps one and two are universal for other technologies. In the step two you will choose a concrete *cpp17* template. For more general information about first steps with ApiGear [First Steps](/docs/guide/quick-start.md) The quick start enables only basic features: the [api](/template-cpp17/docs/features/api.md) generation and simple [stub](/template-cpp17/docs/features/stubs.md) implementation. For all available features check the [overview](/template-cpp17/docs/features.md). ## 1. Install the Code Generator[​](#1-install-the-code-generator "Direct link to 1. Install the Code Generator") ApiGear provides two powerful tools: a Studio and a Command Line Interface (CLI). The Studio is a graphical tool for creating, editing API modules and projects, while the CLI is a command-line tool for generating code from API modules. Both offer comprehensive functionality, including SDK template management, API project creation, code generation, API monitoring, and API simulation. [Download Studio](https://github.com/apigear-io/studio/releases/latest) [Download CLI](https://github.com/apigear-io/cli/releases/latest) ### Install ApiGear Studio[​](#install-apigear-studio "Direct link to Install ApiGear Studio") ApiGear Studio is available for macOS, Windows, and Linux. You can download it from the [ApiGear Studio GitHub releases page](https://github.com/apigear-io/studio/releases/latest). For more detailed information on ApiGear Studio, refer to the dedicated [studio documentation](/docs/studio/intro.md). ### Install ApiGear CLI[​](#install-apigear-cli "Direct link to Install ApiGear CLI") The ApiGear CLI is a versatile command-line tool for generating code from API modules. It's available for macOS, Windows, and Linux. Download the latest version from the [ApiGear CLI GitHub releases page](https://github.com/apigear-io/cli/releases/latest). The CLI offers all the features available in the Studio. For more detailed information on the ApiGear CLI, consult the dedicated [CLI documentation](/docs/cli/intro.md). ## 2. Obtain the Template[​](#2-obtain-the-template "Direct link to 2. Obtain the Template") There are several methods to obtain the template: installation via the Studio, installation via the CLI, or cloning/downloading from GitHub. This step is optional The ApiGear Studio and the CLI detect the specified template in the [solution document](#solution-file) and install it automatically. Optional steps (click to expand) ### Installation via CLI[​](#installation-via-cli "Direct link to Installation via CLI") When using the CLI, only the highlighted line needs to be executed. You can verify the successful installation using the `template cache` command. * Unreal Engine * C++14 * Qt6 * Python ``` $ apigear template install apigear-io/template-unreal@v3.2.2 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-unreal@v3.2.2 | https://github.com/apigear-io/template-unreal.git | | v3.2.2 ... ``` ``` $ apigear template install apigear-io/template-cpp14@v3.6.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-cpp14@v3.6.0 | https://github.com/apigear-io/template-cpp14.git | | v3.6.0 ... ``` ``` $ apigear template install apigear-io/template-qtcpp@v0.4.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-qtcpp@v0.4.0 | https://github.com/apigear-io/template-qtcpp.git | | v0.4.0 ... ``` ``` $ apigear template install apigear-io/template-python@v1.0.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-python@v1.0.0 | https://github.com/apigear-io/template-python.git | | v1.0.0 ... ``` ### Installation via Studio[​](#installation-via-studio "Direct link to Installation via Studio") Installing the template through the Studio is straightforward: * Unreal Engine * C++14 * Qt6 * Python 1. Open an existing project or create a new one 2. Navigate to the `Templates` tab 3. Click `Install` on the `apigear-io/template-unreal` entry 1) Open an existing project or create a new one 2) Navigate to the `Templates` tab 3) Click `Install` on the `apigear-io/template-cpp14` entry 1. Open an existing project or create a new one 2. Navigate to the `Templates` tab 3. Click `Install` on the `apigear-io/template-qtcpp` entry 1) Open an existing project or create a new one 2) Navigate to the `Templates` tab 3) Click `Install` on the `apigear-io/template-python` entry ![Installing the template](/img/apigear-studio-install-unreal-template.png) Figure: Installing the template ### Cloning from GitHub[​](#cloning-from-github "Direct link to Cloning from GitHub") If you need to inspect or modify the template's source code, cloning or downloading the repository is recommended. The repository doesn't need to be part of your project and can be stored anywhere on your computer. * Unreal Engine * C++14 * Qt6 * Python ``` $ git clone https://github.com/apigear-io/template-unreal.git ``` ``` $ git clone https://github.com/apigear-io/template-cpp14.git ``` ``` $ git clone https://github.com/apigear-io/template-qtcpp.git ``` ``` $ git clone https://github.com/apigear-io/template-python.git ``` You can then configure the solution file to use your template by providing the relative path from the solution file to the template directory. ## 3. Set Up the Project[​](#3-set-up-the-project "Direct link to 3. Set Up the Project") A typical project requires two files: a solution file specifying the APIs and the template to use, and at least one API module file. Ideally, both should be placed in a folder named `apigear`, adjacent to each other. Alternatively, you can use the Studio to create a new project and modify the two example files provided. ### Solution File[​](#solution-file "Direct link to Solution File") Create a [solution](/docs/guide/quick-start.md#solution-file) file. The example below specifies: * Module files in *line 8*, here the `helloworld.module.yaml` module with the `Hello` API * The output directory for generated files in *line 9* * The template used to generate the code in *line 10* (this can also be a path to a local copy of the template) * The enabled features of the template in *line 13*, here the `stubs` feature, which provides a simple implementation of interfaces. - Unreal Engine - C++14 - Qt6 - Python helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: ue_docs inputs: - helloworld.module.yaml output: ../ue_docs template: apigear-io/template-unreal@v3.2.2 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: cpp_hello_world inputs: - helloworld.module.yaml output: ../cpp_hello_world template: apigear-io/template-cpp14@v3.6.0 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: qt_hello_world inputs: - helloworld.module.yaml output: ../qt_hello_world template: apigear-io/template-qtcpp@v0.4.0 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: hello_world inputs: - helloworld.module.yaml output: ../py_hello_world template: apigear-io/template-python@v1.0.0 force: true features: - stubs ``` Targets You can extend this solution file with additional targets, each for a different technology with a different template. The `module.yaml` file is technology-independent and can be used with any template. Template Version We highly recommend specifying the exact version of the template. Otherwise, a newer version will be automatically downloaded and used for code generation. This can lead to unexpected behavior if file structures or code changes in the template. note Set the `force` parameter to `true` if you want to always override all generated files. When set to `false`, some files, such as implementations (stub feature), won't be updated. API files are always updated regardless of this setting. ### API Module File[​](#api-module-file "Direct link to API Module File") Use your preferred text editor to create the `helloworld.module.yaml` file with the following example content: helloworld.module.yaml ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` ## 4. Generate Code[​](#4-generate-code "Direct link to 4. Generate Code") ### Generate via CLI[​](#generate-via-cli "Direct link to Generate via CLI") The following snippet demonstrates how to run the CLI and provides an example output: ``` $ apigear generate solution apigear/helloworld.solution.yaml 10:52:20 INF generated 21 files in 30ms. (20 write, 0 skip, 1 copy) topic=gen ``` * The `generate` command instructs the CLI to generate code * The `solution` parameter specifies that we want to process a solution file ### Generate via Studio[​](#generate-via-studio "Direct link to Generate via Studio") 1. Open the project 2. Navigate to the `Solutions` tab 3. Click `Run` on the `helloworld.solution.yaml` entry ![Generating code](/img/apigear-studio-generate-code.png) Figure: Generating code ## 5. Use the generated cpp project[​](#5-use-the-generated-cpp-project "Direct link to 5. Use the generated cpp project") ### Project folder structure[​](#project-folder-structure "Direct link to Project folder structure") With the output directory set as in the example, both *ApiGear* files reside in an `apigear` subfolder next to the *cpp* generated files. In this case the folder structure should look similar to this ``` 📂hello-world ┣ 📂apigear ┃ ┣ 📜helloworld.solution.yaml ┃ ┗ 📜helloworld.module.yaml ┣ 📂cpp_hello_world ┃ ┣ 📂modules ┃ ┃ ┗ 📂io_world ┃ ┃ ┃ ┣ 📂generated ┃ ┃ ┃ ┣ 📂implementation ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┣ 📜hello.cpp ┃ ┃ ┃ ┃ ┣ 📜hello.h ┃ ┃ ┃ ┃ ┗ 📜hello.test.cpp ┃ ┗ 📜CMakeLists.txt ``` Using the solution file from the previous paragraph the code will be generated in the `cpp_hello_world` folder. With subfolder for each module, here `io_world` as the name of module (defined in line 2 of `helloworld.module.yaml`). It contains both features generated: a basic api and a stub implementation. The generated code provides cpp *C++* implementations. The following paragraphs show how you can use it. You can start your project loading the top level CMakeLists.txt in `cpp_hello_world` folder. The 'io\_world/io\_world/generated/api/' folder contains all definitions of the enums and structs for your module, as well as the interface classes for your Interfaces. From now on you can simply include the header files for the api interface or the stub implementation and use it. For more details on generated features please check [api](/template-cpp17/docs/features/api.md), [stubs](/template-cpp17/docs/features/stubs.md). tip Check our "examples" with all features enabled to get more working examples. note For the simulation check [the olink feature](/template-cpp17/docs/features/olink.md) which provides middle layer on your code side and the [simulation](/docs/scripting/backends/intro.md) explained. ### Create and run an example[​](#create-and-run-an-example "Direct link to Create and run an example") Prepare an `examples` folder in the `hello-world/cpp_hello_world` directory with a `main.cpp` like this: ``` #include "io_world/implementation/hello.h" #include int main(){ HelloWorldExample::IoWorld::Hello myHelloInstance; // Try out properties: subscribe for changes myHelloInstance._getPublisher().subscribeToLastChanged( [](const HelloWorldExample::IoWorld::Message& last) { std::cout << "last property changed "; }); // and ask for change. HelloWorldExample::IoWorld::Message messageForProperty; messageForProperty.m_content "New message"; myHelloInstance.setLast(messageForProperty); // Check the signals with subscribing for its change myHelloInstance._getPublisher().subscribeToJustSaid( [](const HelloWorldExample::IoWorld::Message& msg) { qDebug() << "justSaid signal emitted "; }); // and emit one. HelloWorldExample::IoWorld::Message messageForSignal; messageForSignal.m_content "Message from signal"; emit myHelloInstance.justSaid(messageForSignal); // Play around executing operations, maybe they emit signals? or change the properties? auto method_result = myHelloInstance.say(HelloWorldExample::IoWorld::Message(), HelloWorldExample::IoWorld::When::Now); return 0; } ``` add a `CMakeLists.txt` to the `examples` folder: ``` cmake_minimum_required(VERSION 3.1) project(MyExample) # append local binary directory for conan packages to be found list(APPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(MyExample main.cpp) # ensure maximum compiler support if(NOT MSVC) target_compile_options(app PRIVATE -Wall -Wextra -Wpedantic -Werror -fvisibility=hidden) else() target_compile_options(app PRIVATE /W4 /WX /wd4251) endif() find_package(io_world QUIET COMPONENTS io_world-core io_world-implementation ) target_link_libraries(app io_world::io_world-core io_world::io_world-implementation ) install(TARGETS app RUNTIME DESTINATION bin COMPONENT Runtime) ``` Add the `examples` subdirectory to the `hello-world/cpp_hello_world/CMakeLists.txt`. Now you can build the application and the libraries with it and run your example. tip If generating all features, you'll need Poco library (websockets library used by OLink feature) or Paho (Mqtt library used by mqtt feature)'. You then may want to use [conan](/template-cpp17/docs/features/conan.md) feature, which will get all necessary dependencies. Otherwise you might want to add `set(CMAKE_MODULE_PATH "path/to/your/poco_or_paho_build")` to your `CMakeLists` or use option `-DCMAKE_PREFIX_PATH=$GITHUB_WORKSPACE/path/to/poco_or_paho_build` . You might want also use conan for first time - to get you all those dependencies and then use paths provided by conan. --- # Features This guide explains how to use the generated code, what features are available, and their benefits. info A feature is a part of the template that generates a specific aspect of the code. For example, the `api` feature generates the core interfaces and data types. ## Get started[​](#get-started "Direct link to Get started") This template generates a Java and Android SDK from your API definitions. The generated code ranges from pure Java interfaces and data types to a full Android service/client architecture with Messenger-based IPC. note Basic Java knowledge is required. For Android features, familiarity with Android Services, Messenger IPC, and Gradle multi-module projects will help. ### Code generation[​](#code-generation "Direct link to Code generation") Follow the documentation for [code generation](/docs/guide/quick-start.md) in general and [CLI](/docs/cli/generate.md) or the [Studio](/docs/studio/intro.md) tools. Or try the [quick start guide](/template-java/docs/quickstart.md) first, which shows how to prepare an API and generate code from it. tip For questions regarding this template please go to our [discussions page](https://github.com/orgs/apigear-io/discussions). For feature requests or bug reports please use the [issue tracker](https://github.com/apigear-io/template-java/issues). ### Example API[​](#example-api "Direct link to Example API") The following code snippet contains the *API* definition which is used throughout this guide to demonstrate the generated code and its usage. Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` ## Available Features[​](#available-features "Direct link to Available Features") ### Core Features[​](#core-features "Direct link to Core Features") Core features generate Java types and implementations from your API definition: * [api](/template-java/docs/features/api.md) - generates interfaces, event listeners, abstract base classes, enums, and structs as a Gradle multi-module project * [stubs](/template-java/docs/features/stubs.md) - generates ready-to-use implementation classes with default behavior, property change detection, and async support ### Extended Features[​](#extended-features "Direct link to Extended Features") Extended features add Android IPC and native integration: * [android](/template-java/docs/features/android.md) - generates Android Messenger-based service/client architecture for cross-process and cross-app communication * [jnibridge](/template-java/docs/features/jnibridge.md) - generates JNI bridge classes for integrating with native C++ code, such as Unreal Engine ### Test Features[​](#test-features "Direct link to Test Features") * `testserviceapp` - generates an Android test application for the service side, with UI controls for setting properties, emitting signals, and managing the service lifecycle * `testclientapp` - generates an Android test application for the client side, with UI controls for binding to a service, setting properties, and calling operations ### Example Features[​](#example-features "Direct link to Example Features") * `example` - generates a standalone example application that instantiates all key classes to verify the SDK compiles and links correctly Each feature can be selected using the solution file or via the command line tool. note *Features are case-sensitive. Make sure to always **use lowercase.*** tip The *meta* feature `all` enables all specified features of the template. If you want to see the full extent of the generated code, `all` is the easiest solution. Please note, `all` is part of the code generator and not explicitly used within templates. ## Architecture[​](#architecture "Direct link to Architecture") The features listed above compose into a layered runtime architecture: *Your application programs against the generated API interfaces. Stubs provide local implementations, Android service/client enables cross-process communication via Messenger IPC, and the JNI bridge connects to native C++ code.* **Key architectural points:** * Communication between apps uses the **Android Messenger** framework — a lightweight IPC mechanism built on `Handler` and `Binder` * Each API interface gets **its own dedicated service and client**. A module with multiple interfaces (e.g. `Hello` and `Goodbye`) generates separate, independent service/client pairs for each. * The client (`HelloClient`) implements the same `IHello` interface as the local stubs, so your application code works identically regardless of whether the backend is local or remote ## Folder structure[​](#folder-structure "Direct link to Folder structure") This graph shows the folder structure generated for a module with all features enabled. Each API module becomes a Gradle composite build with multiple sub-modules. ``` 📂java_hello_world ┣ 📜build.gradle ┣ 📜settings.gradle ┣ 📂ioWorld ┃ ┣ 📜settings.gradle ┃ ┣ 📂ioWorld_api # api feature ┃ ┃ ┗ 📂src/main/java/ioWorld/ioWorld_api ┃ ┃ ┣ 📜IHello.java ┃ ┃ ┣ 📜IHelloEventListener.java ┃ ┃ ┣ 📜AbstractHello.java ┃ ┃ ┣ 📜Message.java ┃ ┃ ┗ 📜When.java ┃ ┣ 📂ioWorld_impl # stubs feature ┃ ┃ ┗ 📂src/main/java/ioWorld/ioWorld_impl ┃ ┃ ┗ 📜HelloService.java ┃ ┣ 📂ioWorld_android_messenger # android feature ┃ ┃ ┗ 📂src/main/java/ioWorld/ioWorld_android_messenger ┃ ┃ ┣ 📜HelloMessageType.java ┃ ┃ ┣ 📜HelloParcelable.java ┃ ┃ ┣ 📜MessageParcelable.java ┃ ┃ ┗ 📜WhenParcelable.java ┃ ┣ 📂ioWorld_android_service # android feature ┃ ┃ ┗ 📂src/main/java/ioWorld/ioWorld_android_service ┃ ┃ ┣ 📜HelloServiceAdapter.java ┃ ┃ ┣ 📜HelloServiceProvider.java ┃ ┃ ┣ 📜HelloServiceStarter.java ┃ ┃ ┣ 📜HelloBaseServiceLifecycleController.java ┃ ┃ ┗ 📜IHelloServiceProvider.java ┃ ┣ 📂ioWorld_android_client # android feature ┃ ┃ ┗ 📂src/main/java/ioWorld/ioWorld_android_client ┃ ┃ ┗ 📜HelloClient.java ┃ ┣ 📂ioWorldjniservice # jnibridge feature ┃ ┣ 📂ioWorldjniclient # jnibridge feature ┃ ┣ 📂ioWorldserviceexample # testserviceapp feature ┃ ┗ 📂ioWorld_client_example # testclientapp feature ┗ 📂javaHelloWorld_example # example feature ``` note The module name `io.world` is converted to `ioWorld` for the Gradle project and package naming. Each feature generates one or more Gradle sub-modules within the composite build. --- # Android Feature The `android` feature generates Android Messenger-based service/client pairs for cross-process and cross-app communication. Each API interface gets its own dedicated Android `Service` and client — a module with multiple interfaces generates independent service/client pairs for each. The client implements the same `IHello` interface as the local [stubs](/template-java/docs/features/stubs.md), so your application code works identically regardless of whether the backend is local or remote. Under the hood, all communication uses Android's [Messenger](https://developer.android.com/reference/android/os/Messenger) framework, a lightweight IPC mechanism built on `Handler` and `Binder`. info This feature requires the [api](/template-java/docs/features/api.md) and [stubs](/template-java/docs/features/stubs.md) features. The stubs provide the default backend implementation. Minimum Android version This feature requires **API 33 (Android 13 / Tiramisu)** or higher. The generated Messenger IPC code uses the type-safe `Bundle.getParcelable(String, Class)` and `Bundle.getParcelableArray(String, Class)` methods introduced in API 33. All generated modules set `minSdk 33` and `compileSdk 35`. ## Architecture[​](#architecture "Direct link to Architecture") *The client app uses `HelloClient` (which implements `IHello`) like any local implementation. Under the hood, property changes, operations, and signals are marshalled as Android `Message` objects via Messenger IPC.* ## Generated Modules[​](#generated-modules "Direct link to Generated Modules") The feature generates three Gradle modules: | Module | Type | Purpose | | --------------------------- | --------------- | ------------------------------------------------------------ | | `ioWorld_android_messenger` | Android library | Message types and Parcelable wrappers for IPC serialization | | `ioWorld_android_service` | Android library | Service adapter, provider, starter, and lifecycle controller | | `ioWorld_android_client` | Android library | Client that implements `IHello` via Messenger IPC | ## Messenger Module[​](#messenger-module "Direct link to Messenger Module") The `ioWorld_android_messenger` module contains the IPC serialization layer. For each struct and enum in your API, `Parcelable` wrappers are generated (e.g. `MessageParcelable`, `WhenParcelable`) along with a `HelloMessageType` enum defining the wire protocol. You do not interact with these types directly — they are used internally by the service adapter and client. ## Service Module[​](#service-module "Direct link to Service Module") The service module provides the Android `Service` that hosts the backend implementation and handles IPC messages from clients. ### File Overview[​](#file-overview "Direct link to File Overview") ``` 📂ioWorld/ioWorld_android_service ┣ 📜build.gradle ┣ 📜AndroidManifest.xml ┗ 📂src/main/java/ioWorld/ioWorld_android_service ┣ 📜HelloServiceAdapter.java ┣ 📜IHelloServiceProvider.java ┣ 📜HelloServiceProvider.java ┣ 📜HelloServiceStarter.java ┗ 📜HelloBaseServiceLifecycleController.java ``` ### HelloServiceAdapter[​](#helloserviceadapter "Direct link to HelloServiceAdapter") The core Android `Service` class. It bridges the backend `IHello` implementation to IPC clients by: * Receiving property changes and RPC calls from clients, delegating them to the backend * Listening for backend events and broadcasting them to all connected clients * Sending an initial property snapshot when a client registers ### HelloServiceProvider[​](#helloserviceprovider "Direct link to HelloServiceProvider") Singleton that creates a `HelloService` (from [stubs](/template-java/docs/features/stubs.md)) as the backend: ``` IHello backend = HelloServiceProvider.get().getServiceInstance(); ``` You can replace this with a custom `IHelloServiceProvider` to use a different backend. ### HelloServiceStarter[​](#helloservicestarter "Direct link to HelloServiceStarter") Convenience class for managing the service lifecycle. `start(Context)` creates the backend via `HelloServiceProvider`, starts the Android service, and returns the backend instance. `stop(Context)` stops the service and cleans up: ``` // In your Activity's onCreate or service initialization HelloService backend = (HelloService) HelloServiceStarter.start(getApplicationContext()); // You can interact with the backend directly — changes are forwarded to all IPC clients backend.addEventListener(myListener); backend.setLast(new Message("Hello")); // In onDestroy or when shutting down HelloServiceStarter.stop(getApplicationContext()); ``` The starter also exposes a `ServiceLifecycleListener` interface to receive connection and binding-death events. ### AndroidManifest[​](#androidmanifest "Direct link to AndroidManifest") The service module declares the `HelloServiceAdapter` in its `AndroidManifest.xml`: ``` ``` The full generated manifest also includes: ``` ``` ## Client Module[​](#client-module "Direct link to Client Module") The client module provides a transparent IPC proxy that implements `IHello`. ### File Overview[​](#file-overview-1 "Direct link to File Overview") ``` 📂ioWorld/ioWorld_android_client ┣ 📜build.gradle ┗ 📂src/main/java/ioWorld/ioWorld_android_client ┗ 📜HelloClient.java ``` ### HelloClient[​](#helloclient "Direct link to HelloClient") `HelloClient` extends `AbstractHello` and implements `ServiceConnection`. It provides the same `IHello` interface as the local stubs, but all operations are forwarded to the remote service via Messenger IPC: ``` HelloClient client = new HelloClient(context, "myConnection"); // Bind to a service running in another app client.bindToService("com.example.serviceapp"); // Wait for ready state client.addEventListener(new IHelloEventListener() { @Override public void on_readyStatusChanged(boolean isReady) { if (isReady) { // Now safe to use the client client.setLast(new Message("Hello")); } } @Override public void onLastChanged(Message newValue) { // Received property change from service } @Override public void onJustSaid(Message msg) { // Received signal from service } }); ``` **Key behaviors:** * **`bindToService(packageName)`** — binds to the `HelloServiceAdapter` running in the given package * **`unbindFromService()`** — disconnects from the service * **`_isReady()`** — returns `true` when bound and connected; listen for `on_readyStatusChanged` to react * **Property sync** — on connection, the service sends current property values to synchronize the client * **Async RPC** — `sayAsync()` returns a `CompletableFuture` that resolves when the service responds; pending calls resolve with `null` on disconnect. If you unbox the result to a primitive type (e.g., `int`), check for `null` first to avoid a `NullPointerException`. Extern types and class loaders The generated Parcelable wrappers use `SomeParcelable.class.getClassLoader()` to set a class loader for deserialization. This resolves classes that are statically compiled into the APK, but fails for extern types loaded from dynamic libraries. If your extern types are not on the APK's static classpath, you must provide a custom `ClassLoader` that can resolve them. ## Quick Reference[​](#quick-reference "Direct link to Quick Reference") **Service app** — start the service and get the backend: ``` HelloService backend = (HelloService) HelloServiceStarter.start(getApplicationContext()); // Interact with backend directly; stop when done: HelloServiceStarter.stop(getApplicationContext()); ``` **Client app** — bind to the service and use the `IHello` interface: ``` HelloClient client = new HelloClient(getApplicationContext(), "main"); client.bindToService("com.example.serviceapp"); // Use after on_readyStatusChanged(true): client.setLast(new Message("Hello")); client.sayAsync(new Message("World"), When.Now) .thenAccept(result -> Log.i("Client", "say returned: " + result)); // Later: client.unbindFromService(); ``` --- # API Feature The `api` feature is the foundation for code generation. It generates: * Java interfaces for each API interface * Event listener interfaces for property changes, signals, and ready-state * Abstract base classes with thread-safe listener management * Data types: classes for structs, enums with Jackson serialization annotations * Async operation signatures via `CompletableFuture` note The `api` feature generates interface definitions only. For a working implementation, also enable the [stubs](/template-java/docs/features/stubs.md) feature. ## File overview[​](#file-overview "Direct link to File overview") Using the example API definition: Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` The following file structure is generated in the `ioWorld_api` module: ``` 📂ioWorld/ioWorld_api ┣ 📜build.gradle ┗ 📂src/main/java/ioWorld/ioWorld_api ┣ 📜IHello.java ┣ 📜IHelloEventListener.java ┣ 📜AbstractHello.java ┣ 📜RemoteOperationException.java ┣ 📜Message.java ┗ 📜When.java ``` Each API type gets its own source file. For a module with multiple interfaces, each interface generates its own `I{Name}.java`, `I{Name}EventListener.java`, and `Abstract{Name}.java`. ## Enums[​](#enums "Direct link to Enums") For each enum in your API, a Java enum is generated with integer-backed values and Jackson serialization: ``` public enum When { @JsonProperty("0") Now(0), @JsonProperty("1") Soon(1), @JsonProperty("2") Never(2); private final int value; When(int value) { this.value = value; } public int getValue() { return value; } public static When fromValue(int value) { for (When e : values()) { if (e.value == value) return e; } throw new IllegalArgumentException("Unknown int value: " + value); } } ``` Each enum includes: * Integer-backed values matching the API definition * `getValue()` to retrieve the integer value * `fromValue(int)` static factory for deserialization * Jackson `@JsonProperty` annotations for JSON serialization ## Structs[​](#structs "Direct link to Structs") For each struct in your API, a Java class is generated: ``` public class Message { @JsonProperty("content") public String content; public Message(String content) { this.content = content; } public Message() { } public Message(Message other) { this.content = other.content; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Message)) return false; Message other = (Message) o; return this.content == other.content; } @Override public int hashCode() { int result = 7; result = 31 * result + (content != null ? content.hashCode() : 0); return result; } } ``` Each struct includes: * **All-args constructor** — initialize all fields at construction * **No-arg constructor** — useful for frameworks and default initialization * **Copy constructor** — create a copy from another instance * **`equals` / `hashCode`** — equality checks for use in collections and change detection * **Jackson `@JsonProperty`** annotations for JSON serialization caution The generated `equals()` uses `==` for object fields (e.g., `this.content == other.content`), which performs reference comparison rather than value comparison. For String fields, this works with interned strings but may fail for dynamically constructed values. Consider modifying the generated `equals()` to use `Objects.equals()` if value equality is needed. note The generated structs use [Jackson](https://github.com/FasterXML/jackson) annotations (`@JsonProperty`) for JSON serialization. Add the Jackson dependency to your project: ``` implementation 'com.fasterxml.jackson.core:jackson-annotations:2.17.0' ``` ## Interfaces[​](#interfaces "Direct link to Interfaces") For each interface in your API module, an `IHello` interface is generated with property accessors, operations, and event management: ``` public interface IHello { // properties void setLast(Message last); Message getLast(); void fireLastChanged(Message newValue); // methods int say(Message msg, When when); CompletableFuture sayAsync(Message msg, When when); // signals void fireJustSaid(Message msg); // ready state boolean _isReady(); void fire_readyStatusChanged(boolean isReady); // listener management void addEventListener(IHelloEventListener listener); void removeEventListener(IHelloEventListener listener); } ``` ### Properties[​](#properties "Direct link to Properties") Properties are exposed through getter/setter methods. Each property also has a `fire` method to notify listeners of changes: * **`setLast(Message)`** — set the property value * **`getLast()`** — get the current value * **`fireLastChanged(Message newValue)`** — notify all listeners of a change ### Operations[​](#operations "Direct link to Operations") Operations are generated with two variants: #### Synchronous[​](#synchronous "Direct link to Synchronous") The basic synchronous method blocks until completion: ``` int say(Message msg, When when); ``` #### Asynchronous with CompletableFuture[​](#asynchronous-with-completablefuture "Direct link to Asynchronous with CompletableFuture") For non-blocking code, use the async variant: ``` CompletableFuture sayAsync(Message msg, When when); ``` Usage example: ``` // Non-blocking call — returns immediately CompletableFuture future = hello.sayAsync(msg, When.Now); // Option 1: chain a callback future.thenAccept(result -> System.out.println("say returned: " + result)); // Option 2: wait for result (blocks current thread) int result = future.get(); ``` ### Ready State[​](#ready-state "Direct link to Ready State") Each interface includes a ready-state mechanism: * **`_isReady()`** — returns whether the implementation is ready to handle requests * **`fire_readyStatusChanged(boolean)`** — notifies listeners when the ready state changes This is particularly useful for the [android](/template-java/docs/features/android.md) feature where the client becomes ready after binding to the service. ## Event Listeners[​](#event-listeners "Direct link to Event Listeners") For each interface, an event listener is generated to receive property changes, signals, and ready-state notifications: ``` public interface IHelloEventListener { void onLastChanged(Message newValue); void onJustSaid(Message msg); void on_readyStatusChanged(boolean isReady); } ``` * **`onLastChanged(Message newValue)`** — called when a property changes; receives the new value * **`onJustSaid(Message msg)`** — called when a signal is emitted * **`on_readyStatusChanged(boolean isReady)`** — called when the ready state changes ## Abstract Base Class[​](#abstract-base-class "Direct link to Abstract Base Class") The `AbstractHello` class provides a partial implementation with thread-safe listener management: ``` public abstract class AbstractHello implements IHello { private Collection listeners = ConcurrentHashMap.newKeySet(); @Override public void addEventListener(IHelloEventListener listener) { listeners.add(listener); } @Override public void removeEventListener(IHelloEventListener listener) { listeners.remove(listener); } @Override public void fireLastChanged(Message newValue) { for (IHelloEventListener listener : listeners) { listener.onLastChanged(newValue); } } @Override public void fireJustSaid(Message msg) { for (IHelloEventListener listener : listeners) { listener.onJustSaid(msg); } } @Override public void fire_readyStatusChanged(boolean isReady) { for (IHelloEventListener listener : listeners) { listener.on_readyStatusChanged(isReady); } } } ``` The abstract class provides: * **Thread-safe listener collection** using `ConcurrentHashMap.newKeySet()` — safe for concurrent add/remove/iterate * **Fire method implementations** — iterate all listeners and dispatch events * **No property storage** — implementations must provide their own storage (see [stubs](/template-java/docs/features/stubs.md)) To create your own implementation, extend this class and implement the remaining abstract methods (`setLast`, `getLast`, `say`, `sayAsync`, `_isReady`). Alternatively, enable the [stubs](/template-java/docs/features/stubs.md) feature to generate a ready-to-use implementation. ## Error Handling[​](#error-handling "Direct link to Error Handling") The `api` feature generates a `RemoteOperationException` class per module for propagating errors across the IPC boundary. When a backend operation throws an exception, the service catches it, classifies it with an error code, and sends the error back to the client. The client wraps it in a `RemoteOperationException` and completes the `CompletableFuture` exceptionally. ``` public class RemoteOperationException extends RuntimeException { public static final int ERROR_UNKNOWN = 0; public static final int ERROR_SERVICE_DISCONNECTED = 1; public static final int ERROR_SERVICE_NOT_READY = 2; public static final int ERROR_INVALID_ARGUMENT = 3; public static final int ERROR_NOT_IMPLEMENTED = 4; public static final int ERROR_INTERNAL = 5; public int getErrorCode(); } ``` ### Error codes[​](#error-codes "Direct link to Error codes") | Code | Constant | Meaning | Source | | ---- | ---------------------------- | ---------------------------- | ----------------------------------------------- | | 0 | `ERROR_UNKNOWN` | Generic/unclassified error | Default when no code is provided | | 1 | `ERROR_SERVICE_DISCONNECTED` | Binder connection lost | Client detects disconnect | | 2 | `ERROR_SERVICE_NOT_READY` | Backend not ready | Service exists but backend not ready | | 3 | `ERROR_INVALID_ARGUMENT` | Bad input from caller | `IllegalArgumentException` on service side | | 4 | `ERROR_NOT_IMPLEMENTED` | Operation not supported | `UnsupportedOperationException` on service side | | 5 | `ERROR_INTERNAL` | Unexpected backend exception | Default for all other caught exceptions | The service-side mapping uses `instanceof` checks on standard Java exception types. If the backend throws a `RemoteOperationException` itself (e.g., from a nested service call), its error code is preserved. ### Handling errors[​](#handling-errors "Direct link to Handling errors") ``` // Async — check the exception // whenComplete receives RemoteOperationException directly // (not wrapped in CompletionException) because the client // calls completeExceptionally() with it hello.sayAsync(msg, When.Now).whenComplete((result, throwable) -> { if (throwable instanceof RemoteOperationException) { int code = ((RemoteOperationException) throwable).getErrorCode(); // handle based on code } }); // Sync — catch the exception try { hello.say(msg, When.Now); } catch (RemoteOperationException e) { int code = e.getErrorCode(); // handle based on code } ``` note Each module generates its own `RemoteOperationException` in its own package. When handling errors from multiple modules, use `RuntimeException` as a common base type. ## Gradle Project Structure[​](#gradle-project-structure "Direct link to Gradle Project Structure") The `api` feature generates a pure Java library module (`java-library` plugin) with no Android dependencies. This means the API types can be used in any Java project, not just Android. The module includes: * `build.gradle` with `jackson-annotations` dependency * Standard Gradle source layout under `src/main/java/` The parent composite build also generates a `gradle/libs.versions.toml` version catalog for dependency management. --- # JNI Bridge Feature The `jnibridge` feature generates Java classes that bridge the API to native C++ code via JNI (Java Native Interface). This enables native applications — such as Unreal Engine — to use the generated API through the Android service/client architecture. info This feature requires the [api](/template-java/docs/features/api.md), [stubs](/template-java/docs/features/stubs.md), and [android](/template-java/docs/features/android.md) features. It provides both a JNI-backed service implementation and a JNI-forwarding client. note The JNI bridge files are generated as standalone Java files without a Gradle build script, since they are typically integrated into a host build system such as Unreal Engine's build pipeline. ## Overview[​](#overview "Direct link to Overview") The JNI bridge provides two integration points: * **JNI Service** — a backend implementation where all operations are delegated to native C++ code via JNI. Use this when the business logic lives in native code. * **JNI Client** — wraps the `HelloClient` (from the [android](/template-java/docs/features/android.md) feature) and forwards all events to native code via JNI callbacks. Use this when native code needs to consume the API as a client. ## JNI Service[​](#jni-service "Direct link to JNI Service") The JNI service module provides a backend implementation where native C++ code handles all API operations. ### File Overview[​](#file-overview "Direct link to File Overview") ``` 📂ioWorld/ioWorldjniservice ┣ 📜HelloJniService.java ┣ 📜HelloJniServiceProvider.java ┗ 📜HelloJniServiceStarter.java ``` ### HelloJniService[​](#hellojniservice "Direct link to HelloJniService") Extends `AbstractHello` and delegates all property access and operations to `private native` methods (`nativeSetLast`, `nativeGetLast`, `nativeSay`). Your C++ implementation provides the actual logic. Property access is **synchronous and pass-through** — every `getLast()` and `setLast()` call blocks until the native method returns. There is no Java-side property cache. Async operations use a **single-thread executor**, guaranteeing that all async calls are processed in order. The native code can call back into Java to emit events: * **`onLastChanged(Message)`** / **`onJustSaid(Message)`** — forward to all listeners (including the `HelloServiceAdapter` if active) * **`nativeServiceReady(boolean)`** — sets the ready-state flag ### HelloJniServiceProvider[​](#hellojniserviceprovider "Direct link to HelloJniServiceProvider") Singleton that extends `HandlerThread` and implements `IHelloServiceProvider`. Mirrors `HelloServiceProvider` but creates a `HelloJniService` instead of `HelloService`: ``` AbstractHello backend = HelloJniServiceProvider.get().getServiceInstance(); ``` ### HelloJniServiceStarter[​](#hellojniservicestarter "Direct link to HelloJniServiceStarter") Convenience class for managing the service lifecycle with the JNI backend. Like `HelloServiceStarter`, `start(Context)` creates the backend, starts the Android service, and returns the backend instance. `stop(Context)` stops the service: ``` // From native code via JNI HelloJniService backend = (HelloJniService) HelloJniServiceStarter.start(context); // Later HelloJniServiceStarter.stop(context); ``` Connection status changes trigger a native callback: ``` public native void nativeOnAndroidServiceConnectionStatusChanged(boolean isConnected); ``` ## JNI Client[​](#jni-client "Direct link to JNI Client") The JNI client wraps `HelloClient` and forwards all events to native code via JNI callbacks. ### File Overview[​](#file-overview-1 "Direct link to File Overview") ``` 📂ioWorld/ioWorldjniclient ┗ 📜HelloJniClient.java ``` ### HelloJniClient[​](#hellojniclient "Direct link to HelloJniClient") Use this from C++ to consume the API as a client. The JNI client wraps `HelloClient` and exposes methods that native code can call via JNI: ``` // Called from native to connect/disconnect public boolean bind(Context context, String packageName, String connectionID); public void unbind(); // Async RPC with caller-generated callId for native correlation public void sayAsync(String callId, Message msg, When when); ``` All property changes, signals, and RPC results are forwarded to native via the callbacks listed in the table below. ### Async RPC correlation[​](#async-rpc-correlation "Direct link to Async RPC correlation") Unlike the standard `HelloClient` which uses `CompletableFuture`, the JNI client uses a **string-based `callId`** for async RPC correlation. A string-based `callId` is simpler than correlating Java `CompletableFuture` objects across the JNI boundary. The flow works as follows: 1. Native code generates a unique ID (e.g., a GUID) and calls `sayAsync(callId, msg, when)` via JNI 2. The JNI client forwards the call to the underlying `HelloClient` and stores the `callId` 3. When the result arrives, the JNI client calls `nativeOnSayResult(result, callId)` back to native code 4. Native code matches the `callId` to the original request and fulfills the corresponding promise ``` // Native side: initiate an async call with a unique ID FGuid callId = FGuid::NewGuid(); jstring jCallId = env->NewStringUTF(TCHAR_TO_UTF8(*callId.ToString(EGuidFormats::Digits))); env->CallVoidMethod(jniClient, sayAsyncMethodID, jCallId, jMsg, jWhen); // Store callId → promise mapping // Later, Java calls back: // nativeOnSayResult(int result, String callId) // → parse callId, look up the matching promise, fulfill it with the result ``` ## Usage from Native Code[​](#usage-from-native-code "Direct link to Usage from Native Code") The JNI service is designed to be driven from native C++ code. Your native application is responsible for the full lifecycle: starting the Java service, providing the backend logic via native method implementations, forwarding events back to Java, and stopping the service on shutdown. Threading All native method calls (`nativeSetLast`, `nativeGetLast`, `nativeSay`, etc.) arrive on a **dedicated JNI thread**, not on your application's main thread. Your native implementations must account for this — use appropriate synchronization when accessing shared state, and dispatch to the correct thread if your framework requires it (e.g., the game thread in Unreal Engine). The same applies to JNI callbacks from the client side (`nativeOnLastChanged`, `nativeOnSayResult`, etc.). ### Service lifecycle[​](#service-lifecycle "Direct link to Service lifecycle") From native code, use JNI to call `HelloJniServiceStarter.start(context)` and `stop(context)`: 1. **Startup** — find the `HelloJniServiceStarter` Java class and call its static `start` method, passing the Android activity context. This creates the `HelloJniService`, starts the Android service, and returns the Java service instance. Store a global reference to it. 2. **Ready** — once your native backend is initialized, call `nativeServiceReady(true)` on the Java instance to signal that the service is ready. 3. **Shutdown** — call `nativeServiceReady(false)`, then call `HelloJniServiceStarter.stop(context)` and release the Java global reference. ### Implementing native methods[​](#implementing-native-methods "Direct link to Implementing native methods") Your C++ code must provide JNI function implementations for all native methods declared in `HelloJniService`. When Java calls `nativeSetLast(Message)`, `nativeGetLast()`, or `nativeSay(Message, When)`, your native implementation handles the actual logic: ``` // Example: property set — Java calls this when a client sets the property JNI_METHOD void Java_ioWorld_ioWorldjniservice_HelloJniService_nativeSetLast( JNIEnv* Env, jclass Clazz, jobject msg) { // Convert Java Message to native type, then delegate to your backend auto backend = getBackendService(); if (backend) { backend->SetLast(convertFromJava(Env, msg)); } } ``` ### Forwarding events to Java[​](#forwarding-events-to-java "Direct link to Forwarding events to Java") When your native backend changes a property or emits a signal, call back into Java using cached JNI method IDs. At startup, cache the method IDs for `onLastChanged`, `onJustSaid`, and `nativeServiceReady`: ``` // Cache method IDs during initialization jclass serviceClass = env->FindClass("ioWorld/ioWorldjniservice/HelloJniService"); jmethodID onLastChangedID = env->GetMethodID(serviceClass, "onLastChanged", "(LioWorld/ioWorld_api/Message;)V"); // Later, when a property changes in native code: env->CallVoidMethod(javaServiceInstance, onLastChangedID, javaMessageObj); ``` This ensures that property changes and signals originating in native code are forwarded through the Java service to all connected IPC clients. tip The [Unreal Engine template](https://github.com/apigear-io/template-unreal) generates the complete C++ JNI adapter automatically — including JNI method caching, native method implementations, event forwarding, and thread-safe lifecycle management. If you use Unreal Engine, you do not need to write any of this by hand. ## Native Method Summary[​](#native-method-summary "Direct link to Native Method Summary") ### Service-side native methods[​](#service-side-native-methods "Direct link to Service-side native methods") | Method | Direction | Purpose | | -------------------------------------------------------- | ---------- | ---------------------------------- | | `nativeSetLast(Message)` | Java → C++ | Delegate property set to native | | `nativeGetLast()` | Java → C++ | Delegate property get to native | | `nativeSay(Message, When)` | Java → C++ | Delegate operation to native | | `nativeOnAndroidServiceConnectionStatusChanged(boolean)` | Java → C++ | Notify native of service lifecycle | ### Client-side native methods[​](#client-side-native-methods "Direct link to Client-side native methods") | Method | Direction | Purpose | | ------------------------------------------------------------------------------- | ---------- | --------------------------------- | | `nativeOnLastChanged(Message)` | Java → C++ | Forward property change to native | | `nativeOnJustSaid(Message)` | Java → C++ | Forward signal to native | | `nativeOnSayResult(int, String)` | Java → C++ | Forward RPC result to native | | `nativeAsyncOperationFailed(String callId, String errorMessage, int errorCode)` | Java → C++ | Forward RPC failure to native | | `nativeIsReady(boolean)` | Java → C++ | Forward ready state to native | note The `nativeOn{Op}Result` callbacks and `nativeAsyncOperationFailed` are only generated for interfaces that define at least one operation. Interfaces with only properties and signals will not include these callbacks or their associated imports. --- # Stubs The `stubs` feature generates: * Fully instantiable implementation classes for each API interface * A starting point for your business logic * Property storage with change detection and listener notification * Async operation support via `CompletableFuture` and `ExecutorService` The stub implementations extend the abstract base classes generated by the [api](/template-java/docs/features/api.md) feature and provide complete, working implementations that you can customize. note For cross-process communication on Android, see the [android](/template-java/docs/features/android.md) feature. The Android service uses stubs as its default backend. caution The stubs module is an Android library (`com.android.library` plugin) and requires the Android SDK to build. It cannot be used in pure Java (non-Android) projects. ## File overview[​](#file-overview "Direct link to File overview") With our example API definition: Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` The following file structure is generated in the `ioWorld_impl` module: ``` 📂ioWorld/ioWorld_impl ┣ 📜build.gradle ┗ 📂src/main/java/ioWorld/ioWorld_impl ┗ 📜HelloService.java ``` ## Implementation[​](#implementation "Direct link to Implementation") `HelloService` extends `AbstractHello` and provides a complete implementation of the `IHello` interface. It serves as a local backend that can be used directly or as the backend for the [android](/template-java/docs/features/android.md) service adapter. ### Properties[​](#properties "Direct link to Properties") Properties are stored as private fields with change detection in the setter: ``` public class HelloService extends AbstractHello { private Message m_last = new Message(); @Override public void setLast(Message last) { if ((m_last != null && !m_last.equals(last)) || (m_last == null && last != null)) { m_last = last; onLastChanged(m_last); } } private void onLastChanged(Message newValue) { fireLastChanged(newValue); } @Override public Message getLast() { return m_last; } } ``` The setter: 1. Checks if the value actually changed using `equals()` 2. Updates the stored value 3. Calls `onLastChanged()`, which notifies all listeners via the inherited `fireLastChanged()` ### Operations[​](#operations "Direct link to Operations") Synchronous operations are generated with stub implementations that return default values: ``` @Override public int say(Message msg, When when) { return 0; } ``` Fill in your business logic by modifying the method body. ### Async Operations[​](#async-operations "Direct link to Async Operations") Async operations are implemented using `CompletableFuture` backed by a single-thread `ExecutorService`: ``` private static final ExecutorService executor = Executors.newSingleThreadExecutor(); @Override public CompletableFuture sayAsync(Message msg, When when) { return CompletableFuture.supplyAsync( () -> say(msg, when), executor); } ``` The async variant wraps the synchronous implementation, so you only need to implement the sync method. Override `sayAsync` if you need custom async behavior. ### Signals[​](#signals "Direct link to Signals") To emit a signal from your implementation, use the inherited fire methods: ``` Message msg = new Message("Something happened"); fireJustSaid(msg); ``` This notifies all registered event listeners. When the service runs behind the [android](/template-java/docs/features/android.md) adapter, the signal is automatically forwarded to all IPC clients. ### Ready State[​](#ready-state "Direct link to Ready State") The stub service marks itself as ready immediately on construction: ``` public HelloService() { fire_readyStatusChanged(true); } @Override public boolean _isReady() { return isServiceReady; } ``` ## Usage[​](#usage "Direct link to Usage") ### Service side[​](#service-side "Direct link to Service side") The stubs serve as the default backend for the [android](/template-java/docs/features/android.md) service. Use `HelloServiceStarter` to manage the service lifecycle — it creates the backend, starts the Android service, and returns the backend instance: ``` // Start the service and get the backend instance HelloService backend = (HelloService) HelloServiceStarter.start(getApplicationContext()); backend.addEventListener(this); // Interact with the backend directly (changes are forwarded to IPC clients) backend.setLast(new Message("Hello from service")); ``` Stop the service when your activity or application is done: ``` // In onDestroy() or equivalent backend.removeEventListener(this); HelloServiceStarter.stop(getApplicationContext()); ``` The starter also exposes a `ServiceLifecycleListener` interface to receive connection and binding-death events. ### Client side[​](#client-side "Direct link to Client side") From another app, bind to the service using `HelloClient` (from the [android](/template-java/docs/features/android.md) feature). The client implements the same `IHello` interface, so your application code works identically regardless of whether the backend is local or remote: ``` HelloClient client = new HelloClient(getApplicationContext(), "main"); client.bindToService("com.example.serviceapp"); // Wait for the connection to be established client.addEventListener(new IHelloEventListener() { @Override public void on_readyStatusChanged(boolean isReady) { if (isReady) { // Now safe to use the client client.setLast(new Message("Hello from client")); } } @Override public void onLastChanged(Message newValue) { // Received property change from service } @Override public void onJustSaid(Message msg) { // Received signal from service } }); // Later client.unbindFromService(); ``` See the [android](/template-java/docs/features/android.md) feature documentation for the full service/client architecture. ## Thread Safety[​](#thread-safety "Direct link to Thread Safety") The listener collection (inherited from `AbstractHello`) uses `ConcurrentHashMap.newKeySet()`, which is safe for concurrent add/remove/iterate. However, the `ExecutorService` runs async operations on a separate thread. If your operation implementation accesses shared state, ensure proper synchronization. ## Gradle Module[​](#gradle-module "Direct link to Gradle Module") The `ioWorld_impl` module is an Android library (`com.android.library` plugin) that depends on the `ioWorld_api` module. It requires the Android SDK for logging (`android.util.Log`). --- # Template Java This is the documentation for the *Java* template for the [ApiGear](/docs/guide/quick-start.md) code generator. It generates a complete Java and Android SDK from your API definitions, including: * Java interfaces, enums, structs, and abstract base classes * Stub implementations with default behavior * Android service/client architecture with Messenger-based IPC * JNI bridge for native C++ integration ## When to use this template[​](#when-to-use-this-template "Direct link to When to use this template") This template is designed for Android systems where multiple apps communicate through shared APIs — a common pattern in automotive infotainment, IoT gateways, and other embedded Android platforms. In these systems, a service app hosts the business logic (or delegates to native C++ for performance-critical work), and one or more client apps bind to it over IPC using the same interface, as if the call were local. Without code generation, building this architecture means hand-writing Android Service boilerplate, Messenger message handling, Parcelable serialization, property synchronization, and — if native code is involved — JNI bindings for every interface. The template generates all of it from a single API definition. Use this template when you need to: * **Share APIs between Android apps** — The template generates a Messenger-based service/client architecture that handles cross-process IPC. Each API interface gets its own independent service and client pair, so you can compose your system from modular, separately deployable components. Application code works identically whether the backend is local or remote. * **Bridge native C++ code into Android** — The JNI bridge lets native applications (such as Unreal Engine running on Android) participate in the service/client architecture without writing JNI boilerplate by hand. The service app can delegate directly to a C++ implementation while exposing the same Java interface to all clients. * **Generate a complete, build-ready project** — The output is a Gradle multi-module project with proper dependency management. Generated code covers Parcelable serialization, Messenger setup, service binding, async RPC correlation, and property change notifications — the kind of boilerplate that is tedious to write and error-prone to maintain by hand. * **Define APIs once, generate the plumbing** — Pure Java API types (interfaces, enums, structs) can be used in any Java project. The stubs, Android, and JNI layers require the Android SDK and are optional features you enable only when you need them. tip If you only need Java interfaces and data types without Android or JNI integration, you can generate just the `api` and `stubs` features. See [Features](/template-java/docs/features.md) for the full list. ## Requirements[​](#requirements "Direct link to Requirements") The recommended way to build and develop with the generated code is **Android Studio**, which manages the Android SDK, Gradle, and JDK automatically. For command-line or CI builds, the generated project requires: * **JDK 17** or later * **Gradle 8.7+** * **Android SDK** with compileSdk 35 The minimum supported Android version is **API 33 (Android 13 / Tiramisu)**. Java version details The build toolchain requires JDK 17 because the Android Gradle Plugin 8.5.2 depends on it. The generated source code itself targets **Java 11** (`sourceCompatibility = JavaVersion.VERSION_11`) and uses no Java 17 language features. This means the compiled bytecode is Java 11-compatible, while the build machine must have JDK 17 installed. Android 13 (API 33) natively supports Java 11 library APIs. Newer Java language features like records (Java 16), sealed classes (Java 17), and text blocks (Java 15) could technically be used with minSdk 33 — AGP desugars them into compatible bytecode — but the generated code does not use them. ## Documentation[​](#documentation "Direct link to Documentation") * [Quick-Start](/template-java/docs/quickstart.md?current-template=template-java) is the easiest way to get started * [Features](/template-java/docs/features.md) explains the available code generator features, what code is generated, and how to use it --- # Quick-Start The Quick-Start guide explains how to get from an API to a functional *Java* project in a few steps. For more general information about first steps with ApiGear, see [First Steps](/docs/guide/quick-start.md). The quick start uses only the `api` feature. For all available features, see the [overview](/template-java/docs/features.md). ## 1. Install the Code Generator[​](#1-install-the-code-generator "Direct link to 1. Install the Code Generator") ApiGear provides two powerful tools: a Studio and a Command Line Interface (CLI). The Studio is a graphical tool for creating, editing API modules and projects, while the CLI is a command-line tool for generating code from API modules. Both offer comprehensive functionality, including SDK template management, API project creation, code generation, API monitoring, and API simulation. [Download Studio](https://github.com/apigear-io/studio/releases/latest) [Download CLI](https://github.com/apigear-io/cli/releases/latest) ### Install ApiGear Studio[​](#install-apigear-studio "Direct link to Install ApiGear Studio") ApiGear Studio is available for macOS, Windows, and Linux. You can download it from the [ApiGear Studio GitHub releases page](https://github.com/apigear-io/studio/releases/latest). For more detailed information on ApiGear Studio, refer to the dedicated [studio documentation](/docs/studio/intro.md). ### Install ApiGear CLI[​](#install-apigear-cli "Direct link to Install ApiGear CLI") The ApiGear CLI is a versatile command-line tool for generating code from API modules. It's available for macOS, Windows, and Linux. Download the latest version from the [ApiGear CLI GitHub releases page](https://github.com/apigear-io/cli/releases/latest). The CLI offers all the features available in the Studio. For more detailed information on the ApiGear CLI, consult the dedicated [CLI documentation](/docs/cli/intro.md). ## 2. Obtain the Template[​](#2-obtain-the-template "Direct link to 2. Obtain the Template") There are several methods to obtain the template: installation via the Studio, installation via the CLI, or cloning/downloading from GitHub. This step is optional The ApiGear Studio and the CLI detect the specified template in the [solution document](#solution-file) and install it automatically. Optional steps (click to expand) ### Installation via CLI[​](#installation-via-cli "Direct link to Installation via CLI") When using the CLI, only the highlighted line needs to be executed. You can verify the successful installation using the `template cache` command. * Unreal Engine * C++14 * Qt6 * Python ``` $ apigear template install apigear-io/template-unreal@v3.2.2 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-unreal@v3.2.2 | https://github.com/apigear-io/template-unreal.git | | v3.2.2 ... ``` ``` $ apigear template install apigear-io/template-cpp14@v3.6.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-cpp14@v3.6.0 | https://github.com/apigear-io/template-cpp14.git | | v3.6.0 ... ``` ``` $ apigear template install apigear-io/template-qtcpp@v0.4.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-qtcpp@v0.4.0 | https://github.com/apigear-io/template-qtcpp.git | | v0.4.0 ... ``` ``` $ apigear template install apigear-io/template-python@v1.0.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-python@v1.0.0 | https://github.com/apigear-io/template-python.git | | v1.0.0 ... ``` ### Installation via Studio[​](#installation-via-studio "Direct link to Installation via Studio") Installing the template through the Studio is straightforward: * Unreal Engine * C++14 * Qt6 * Python 1. Open an existing project or create a new one 2. Navigate to the `Templates` tab 3. Click `Install` on the `apigear-io/template-unreal` entry 1) Open an existing project or create a new one 2) Navigate to the `Templates` tab 3) Click `Install` on the `apigear-io/template-cpp14` entry 1. Open an existing project or create a new one 2. Navigate to the `Templates` tab 3. Click `Install` on the `apigear-io/template-qtcpp` entry 1) Open an existing project or create a new one 2) Navigate to the `Templates` tab 3) Click `Install` on the `apigear-io/template-python` entry ![Installing the template](/img/apigear-studio-install-unreal-template.png) Figure: Installing the template ### Cloning from GitHub[​](#cloning-from-github "Direct link to Cloning from GitHub") If you need to inspect or modify the template's source code, cloning or downloading the repository is recommended. The repository doesn't need to be part of your project and can be stored anywhere on your computer. * Unreal Engine * C++14 * Qt6 * Python ``` $ git clone https://github.com/apigear-io/template-unreal.git ``` ``` $ git clone https://github.com/apigear-io/template-cpp14.git ``` ``` $ git clone https://github.com/apigear-io/template-qtcpp.git ``` ``` $ git clone https://github.com/apigear-io/template-python.git ``` You can then configure the solution file to use your template by providing the relative path from the solution file to the template directory. ## 3. Set Up the Project[​](#3-set-up-the-project "Direct link to 3. Set Up the Project") A typical project requires two files: a solution file specifying the APIs and the template to use, and at least one API module file. Ideally, both should be placed in a folder named `apigear`, adjacent to each other. Alternatively, you can use the Studio to create a new project and modify the two example files provided. ### Solution File[​](#solution-file "Direct link to Solution File") Create a [solution](/docs/guide/quick-start.md#solution-file) file. The example below specifies: * Module files in *line 8*, here the `helloworld.module.yaml` module with the `Hello` API * The output directory for generated files in *line 9* * The template used to generate the code in *line 10* (this can also be a path to a local copy of the template) * The enabled features of the template in *line 13*, here the `stubs` feature, which provides a simple implementation of interfaces. - Unreal Engine - C++14 - Qt6 - Python helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: ue_docs inputs: - helloworld.module.yaml output: ../ue_docs template: apigear-io/template-unreal@v3.2.2 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: cpp_hello_world inputs: - helloworld.module.yaml output: ../cpp_hello_world template: apigear-io/template-cpp14@v3.6.0 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: qt_hello_world inputs: - helloworld.module.yaml output: ../qt_hello_world template: apigear-io/template-qtcpp@v0.4.0 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: hello_world inputs: - helloworld.module.yaml output: ../py_hello_world template: apigear-io/template-python@v1.0.0 force: true features: - stubs ``` Targets You can extend this solution file with additional targets, each for a different technology with a different template. The `module.yaml` file is technology-independent and can be used with any template. Template Version We highly recommend specifying the exact version of the template. Otherwise, a newer version will be automatically downloaded and used for code generation. This can lead to unexpected behavior if file structures or code changes in the template. note Set the `force` parameter to `true` if you want to always override all generated files. When set to `false`, some files, such as implementations (stub feature), won't be updated. API files are always updated regardless of this setting. ### API Module File[​](#api-module-file "Direct link to API Module File") Use your preferred text editor to create the `helloworld.module.yaml` file with the following example content: helloworld.module.yaml ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` ## 4. Generate Code[​](#4-generate-code "Direct link to 4. Generate Code") ### Generate via CLI[​](#generate-via-cli "Direct link to Generate via CLI") The following snippet demonstrates how to run the CLI and provides an example output: ``` $ apigear generate solution apigear/helloworld.solution.yaml 10:52:20 INF generated 21 files in 30ms. (20 write, 0 skip, 1 copy) topic=gen ``` * The `generate` command instructs the CLI to generate code * The `solution` parameter specifies that we want to process a solution file ### Generate via Studio[​](#generate-via-studio "Direct link to Generate via Studio") 1. Open the project 2. Navigate to the `Solutions` tab 3. Click `Run` on the `helloworld.solution.yaml` entry ![Generating code](/img/apigear-studio-generate-code.png) Figure: Generating code ## 5. Use the generated Java project[​](#5-use-the-generated-java-project "Direct link to 5. Use the generated Java project") Prerequisites The easiest way to work with the generated code is [Android Studio](https://developer.android.com/studio), which manages the SDK, Gradle, and JDK for you. For command-line builds, you need JDK 17 or later (see [Eclipse Adoptium](https://adoptium.net/)), Gradle 8.7+, and the Android SDK with compileSdk 35. The minimum supported Android version is **API 33 (Android 13 / Tiramisu)**. ### Project folder structure[​](#project-folder-structure "Direct link to Project folder structure") For the code generation we assume that both *ApiGear* files reside in an `apigear` subfolder next to the generated files. In this case, the folder structure should look similar to this. ``` 📂hello-world ┣ 📂apigear ┃ ┣ 📜helloworld.solution.yaml ┃ ┗ 📜helloworld.module.yaml ┣ 📂java_hello_world ┃ ┗ 📂ioWorld ┃ ┗ 📂ioWorld_api/src/main/java/ioWorld/ioWorld_api ┃ ┣ 📜IHello.java ┃ ┣ 📜IHelloEventListener.java ┃ ┣ 📜AbstractHello.java ┃ ┣ 📜Message.java ┃ ┗ 📜When.java ``` The generated code is a Gradle multi-module project with separate source files per type. ### Create and run an example[​](#create-and-run-an-example "Direct link to Create and run an example") The `api` feature generates separate files for each type: the `When` enum, the `Message` struct, the `IHello` interface, the `IHelloEventListener` event listener, and the `AbstractHello` abstract base class. You can implement the interface and use it in your project: ``` import ioWorld.ioWorld_api.*; import java.util.concurrent.CompletableFuture; public class HelloExample { // A simple implementation of the Hello interface static class MyHello extends AbstractHello { private Message last = new Message(); @Override public int say(Message msg, When when) { System.out.println("say called: " + msg.content + " (" + when + ")"); return 42; } @Override public CompletableFuture sayAsync(Message msg, When when) { return CompletableFuture.supplyAsync(() -> say(msg, when)); } @Override public void setLast(Message last) { if (!last.equals(this.last)) { this.last = last; fireLastChanged(last); } } @Override public Message getLast() { return last; } @Override public boolean _isReady() { return true; } } public static void main(String[] args) { MyHello hello = new MyHello(); // Use a struct (all-args or no-arg constructor) Message msg = new Message("Hello World"); // Call an operation int result = hello.say(msg, When.Now); System.out.println("Result: " + result); // Subscribe to property changes and signals hello.addEventListener(new IHelloEventListener() { @Override public void onLastChanged(Message newValue) { System.out.println("last changed: " + newValue.content); } @Override public void onJustSaid(Message msg) { System.out.println("justSaid signal: " + msg.content); } @Override public void on_readyStatusChanged(boolean isReady) { System.out.println("ready: " + isReady); } }); // Set a property (triggers onLastChanged) hello.setLast(msg); } } ``` tip For more features, check the [features overview](/template-java/docs/features.md). --- # Features This guide explains how to use the generated code, what are the available features and their benefits. ## Get started[​](#get-started "Direct link to Get started") This template generates code for [*Python*](https://www.python.org/) projects. In order to successfully run the generated code, you need to have *Python* installed (at least 3.11). Check [the Python website](https://www.python.org/downloads/) for downloads. Basic understanding of *Python* is required. ### Code generation[​](#code-generation "Direct link to Code generation") Follow the documentation for the [code generation](/docs/guide/quick-start.md) in general and [CLI](/docs/cli/generate.md) or the [Studio](/docs/studio/intro.md) tools. Or try first the [quick start guide](/template-python/docs/quickstart.md) which shows how to prepare api and generate code out of it. tip For questions regarding this template please go to our [discussions page](https://github.com/orgs/apigear-io/discussions). For feature requests or bug reports please use the [issue tracker](https://github.com/apigear-io/template-python/issues). ### Example API[​](#example-api "Direct link to Example API") The following code snippet contains the *API* which is used throughout this guide to demonstrate the generated code and its usage in *Python*. Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` ## Features[​](#features-1 "Direct link to Features") ### Core[​](#core "Direct link to Core") Core features generate the view model for your `api`. They give you typed data structures, abstract base interfaces, and a runnable project skeleton you can fill in with your own logic. * **`api`** — generates one `api.py` per module containing pydantic-based `BaseModel` structs (with alias-aware JSON encoding), `IntEnum` enums, and an abstract base class `I` for every interface. The abstract methods raise `NotImplementedError`, so subclasses must override them. This feature also drops shared helpers under `utils/` (`eventhook.py`, `base_types.py`). Every other feature in this template depends on `api`. * **`scaffold`** — generates the project skeleton: top-level `Makefile`, `requirements.txt`, root `README.md`, and a per-module `impl/` folder with one ready-to-edit Python file per interface. Each `impl/.py` extends the matching `I` base class and provides a working default implementation: backing fields for properties, `EventHook` instances for property-change events and signals, getters/setters that fire the change events, and operations that return type-correct defaults. A pytest scaffold (`test_.py`) is generated alongside. note The `scaffold` feature is what older versions of these docs called *stubs*. The feature flag you pass in your solution YAML or on the command line is `scaffold` — use that name verbatim. The `test_helpers` feature adds builders under `test_helpers/` that you can use from your own tests. ### Extended[​](#extended "Direct link to Extended") Extended features build on `api` and `scaffold` to expose your interfaces over the network. They each generate per-interface client and server adapters plus a shared protocol layer under the top-level `apigear/` folder. * **`olink`** — generates an [ObjectLink](/docs/protocols/objectlink/intro.md) WebSocket client and server adapter per interface. You get a `Sink` (client side, in `olink/sinks.py`) that implements your `I` and forwards calls over the wire, and a `Source` (server side, in `olink/sources.py`) that wraps your local implementation and exposes it remotely. The shared transport lives in `apigear/olink/` (built on `olink-core`, `websockets`, and `starlette`/`uvicorn`). Use this feature to connect with ApiGear simulation tools or to interoperate with any other ApiGear technology template that supports ObjectLink. See the [olink quickstart](/docs/protocols/objectlink/intro.md) and the generated `examples/olink/` folder. * **`mqtt`** — generates MQTT client and server adapters per interface (`mqtt/sinks.py` and `mqtt/sources.py`) on top of the [Paho MQTT library](https://pypi.org/project/paho-mqtt/). The client adapter `ClientAdapter` subscribes to property/signal/response topics and exposes async operations. The server adapter `ServiceAdapter` wraps your local implementation, publishes property changes and signals, and answers method invocations. The shared transport lives in `apigear/mqtt/`. Worked examples are provided: * `examples/mqtt/server.py` shows the server adapter wrapping your local implementation. * `examples/mqtt/client.py` shows the client adapter consuming a remote service. See the dedicated [MQTT page](/template-python/docs/features/mqtt.md) for usage, scope, and current limitations. There is also an *internal* feature `apigear`, which is generated for the extended features. Its usage is explained alongside the extended features that pull it in. Each feature can be selected using the solution file or via the command-line tool. note \*Features are case sensitive, make sure to always **use lower-case.** \* tip The *meta* feature `all` enables all specified features of the template. If you want to see the full extent of the generated code, `all` is the easiest solution. Note that `all` is part of the code generator and is not explicitly used within templates. ## Folder structure[​](#folder-structure "Direct link to Folder structure") This graph shows the full folder structure which is generated for `all` features enabled. Generated features are encapsulated in separate folders inside the module folder, here `io_world` or for the common features like `examples` and the internal helper feature `apigear`, a level above, in the `generation layer` level, here `qt_hello_world`. For more details visit the documentation for each feature. ``` 📂hello-world ┣ 📂apigear ┃ ┣ 📜helloworld.solution.yaml ┃ ┗ 📜helloworld.module.yaml ┣ 📂py_hello_world ┃ ┣ 📂apigear ┃ ┣ 📂examples ┃ ┃ ┣ 📂mqtt ┃ ┃ ┗ 📂olink ┃ ┣ 📂io_world ┃ ┃ ┣ 📂api ┃ ┃ ┣ 📂impl ┃ ┃ ┣ 📂mqtt ┃ ┃ ┣ 📂olink ┃ ┃ ┗ 📂test_helpers ┃ ┣ 📂utils ┃ ┣ 📜Makefile ┃ ┣ 📜README.md ┃ ┗ 📜requirements.txt ``` --- # MQTT caution **Experimental — API may change in 1.x; not recommended for production.** This feature is the smallest working adapter that maps a generated interface onto the MQTT protocol. The following gaps are known and intentional in the current release: * **No TLS.** The shared client connects in plain TCP. Bring your own `TLSContext` if you need encryption. * **No auth or ACL guidance.** No username/password helper, no broker ACL examples, no credential storage pattern. * **Minimal error handling.** Network and protocol errors are mostly logged; there is no retry queue, no dead-letter handling, no caller-visible error type. * **No reconnect or backoff strategy documented.** The Paho client's network loop is started, but reconnect/backoff/clean-session behavior is not configured for you. * **No QoS strategy guidance.** All publishes and subscribes use a fixed QoS of 2. There is no per-topic tuning and no documentation on when to drop to 1 or 0. Track maturation and file specific issues in the [template-python issue tracker](https://github.com/apigear-io/template-python/issues). note Open design questions in the protocol layer are flagged inline in `apigear/mqtt/base.py` (look for `TODO` comments around the `clean_start` flag and the on-connect resubscribe path). They are surfaced here rather than hidden so that downstream users can weigh in before the API solidifies. To know which files to inspect when filing an issue or reading the code: the protocol-layer client/service implementation lives in `apigear/mqtt/` (shared across all interfaces), and the per-interface client/server adapters live in `{module}/mqtt/sinks.py` and `{module}/mqtt/sources.py`. This feature purpose is not only to help you introduce MQTT protocol into your project, but also show that an existing protocol can be adapted for sharing your data in your ecosystem. When going through this document you may notice this implementation contains general client/server adapters in 📂hello-world/apigear/mqtt and an interface specific part generated from templates for each interface in 📂hello-world/py\_hello\_world/io\_world/mqtt.

This feature provides a *client* and a *server* adapter for your interfaces for the MQTT protocol. It allows you to connect different applications in the same or different technologies (check all of our [templates](/docs/sdk/intro.md)).
Use an *Mqtt client* instead of your interface implementation to be able to receive data from remote service.
Use an *Mqtt server adapter* to expose your interface implementation as a remote service.
tip The MQTT broker is not provided with implementation. To be able to run client and service you need to run a broker of your choice. ### Before Start[​](#before-start "Direct link to Before Start") The mqtt library needs to be installed separately, make sure you have installed all the libraries listed in requirements ``` `pip install --upgrade -r requirements.txt` ``` ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With an example API Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure will be generated. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┃ ... ┣ 📂py_hello_world ┃ ┣ 📂apigear ┃ ┃ ┣ 📂mqtt ┃ ┃ ┃ ┣ 📜base.py ┃ ┃ ┃ ┣ 📜client.py ┃ ┃ ┃ ┣ 📜service.py ┃ ┃ ┃ ┗ 📜__init__.py ┃ ┃ ┃ ┃ ┣ 📂examples ┃ ┣ 📂io_world ┃ ┃ ┣ 📂api ┃ ┃ ┣ 📂impl ┃ ┃ ┣ 📂mqtt ┃ ┃ ┃ ┣ 📜sinks.py ┃ ┃ ┃ ┣ 📜sources.py ┃ ┃ ┃ ┗ 📜__init__.py ... ``` ### Python apigear - The Network Layer[​](#python-apigear---the-network-layer "Direct link to Python apigear - The Network Layer") When generating the mqtt feature (or any of those: olink monitor feature) you'll get an additional folder it the top most directory: the 📂hello-world/📂apigear. The 📂mqtt subfolder contains objects that implement a network layer (based on [Paho Mqtt library](https://pypi.org/project/paho-mqtt/)) for the MQTT protocol. Those are: * Client - Adapts the MQTT client, to serve as an network endpoint for [interface client adapters](/template-python/docs/features/mqtt.md#mqtt-client-adapter). Exposes: * methods that allow receiving data for remote service: subscribing for properties changes, signals emission and method response invocation; * methods that allow remote using the service: requesting property change or invoking a method.
The client may serve many client interface adapters, even for the same interfaces (allows subscribing for same topic). In case many interface client adapters for same interface are connected: property changes and signals are provided to all interface client adapters, but the invoke method response will be delivered only for the one that requested it. * Service - Adapts the MQTT client to serve as an network endpoint for [interface service adapters](/template-python/docs/features/mqtt.md#mqtt-server-adapter). Exposes: * methods that allow receiving requests from remote clients: subscribing for properties change requests, send method invocation; * methods that allow publishing property change, signal, functionality to handles sending a response for method invocation requests.
This Service may be used for many, interface service adapters, but it is not recommended to use more than one interface service adapter for the same interfaces. tip Have in mind that MQTT might not be suitable for high-frequency messages especially with one mqtt client serving more than one object. Also the brokers have limits for messages number/size queued from one client. In case you are not getting all the messages consider changing those or splitting traffic between more clients (maybe some handle the properties, some handle the methods). ### MQTT Client Adapter[​](#mqtt-client-adapter "Direct link to MQTT Client Adapter") File `📜sinks.py` contains the remote client for the `Hello` interface - a `HelloClientAdapter` class.
The object is an `IHello` implementation.
It requires an instance of Apigear::Mqtt::Client to work. It uses the Client to subscribe (and unsubscribe) for topics that allow receiving properties, signals and invoke responses from service. #### Properties[​](#properties "Direct link to Properties") The property getters (here getter `get_last`) return immediately the locally stored last received value from server.
The property setter (here setter `set_last` ) requests setting a value on server side, local value is not changed.
You can add handler for a property changed event (here `on_last_changed(Message)` ) When the client receives information that server changed the property, a target property (here `last`) is updated locally and all handlers added for the event are fired (in addition order) with the new value of property. note The connected interface client adapter has its local properties in sync with a service. The properties messages are retained in mqtt broker, so all already set properties are provided. #### Operations[​](#operations "Direct link to Operations") The operations are async, and they return a coroutine awaited with a timeout of 500 seconds.
The async method sends an invoke operation request to a service and waits for the response.
Have in mind that this is a blocking operation. #### Signals[​](#signals "Direct link to Signals") You should not emit any signals from a client. You can add a handler to any signals offered by your interface (here `void just_said(Message)` ) When a HelloClientAdapter client receives the message from server that indicates the signal was emitted it executes all added handlers to a `on_just_said` event hook. #### Use `HelloClientAdapter`[​](#use-helloclientadapter "Direct link to use-helloclientadapter") HelloClientAdapter is an adapter of you interface to the Mqtt Client (with protocol and network layer implementation), here provided by a `apigear.mqtt.Client`. All you need to do is to pass the `apigear.mqtt.Client` to your Interface Client Adapter, and request connecting to host when it is convenient for you. You can find the example code for the your `Hello` MQTT client below. Remember to run a MQTT broker of your choice. ``` import os import sys # add context - your relative path from this example to py_hello_world dir e.g. like this sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) import apigear.mqtt import logging import io_world.mqtt import io_world.api async def main(): # create a mqtt adapter for client side client = apigear.mqtt.Client("UniqueClientNameForMqttHelloExample") # create mqtt interface adapters for client side mqtt_hello = io_world.mqtt.HelloClientAdapter(client) await client.connect("localhost", 1883) # Subscribe for property changes def handleProperty(value): print("received property change"); print(value); mqtt_hello.on_last_changed += handleProperty # or ask for change. local_last = io_world.api.Message(); local_last.content = "New message" mqtt_hello.set_last(local_last); # Check the signals with subscribing for its change def handleSignal(value): print("received signal"); print(value); mqtt_hello.on_just_said += handleSignal # Play around executing your operations message_to_say = io_world.api.Message() message_to_say.content = "Message to say" result = mqtt_hello.say(message_to_say, io_world.api.When.NOW) await result print("method result") print(result) input("Press Enter to close") client.disconnect() if __name__ == '__main__': loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(main()) ``` ### MQTT Server Adapter[​](#mqtt-server-adapter "Direct link to MQTT Server Adapter") File `📜sources.py` contains the mqtt server side adapter for the `Hello` interface - the `HelloServiceAdapter` object.
When creating the `HelloServiceAdapter` you need to provide the `apigear.mqtt.Service` and the local implementation of `IHello`, local service.
`HelloServiceAdapter` object exposes the local object for remote usage with the MQTT protocol. It handles all the network requests, and calls your local object. The mqtt client connection and communication is handled transparently, no additional actions are needed. #### Properties[​](#properties-1 "Direct link to Properties") The MQTT service adapters add handlers for all the properties changes to the local implementation. On property change the generated implementation executes all added handlers. In this example on each `last` property change the handler for `on_last_changed(Message)`, sends a message with a topic specific for this property in this interface and with value in the payload. This happens either when you change a property directly on your local `Hello` object, or when a change property request message is received by the `HelloServiceAdapter`, which applies the property on your local `Hello` object. #### Operations[​](#operations-1 "Direct link to Operations") The operations invocation which came from the clients through the network will be performed on your local `Hello` object. The result of the operation (if any) will be returned only to a client, from which the message was send, not all clients. #### Signals[​](#signals-1 "Direct link to Signals") The MQTT service adapters add handlers for all the signals to the local implementation which send MQTT messages with information about signal emission with their arguments. On signal emission the generated implementation executes all handlers added for this signal. All the signals emitted by your local `Hello` objects are forwarded to all connected clients. #### Use `HelloServiceAdapter`[​](#use-helloserviceadapter "Direct link to use-helloserviceadapter") `HelloServiceAdapter` an adapter of your interface to the specific, object server side, version of Mqtt Client (with protcol and network layer implementation), here provided by a `ApiGear::Mqtt::ServiceAdapter` All you need to do is to pass this ServiceAdapter to your Interface Service Adapter, and request connecting to host when it is convenient for you. You can find the example code for the your `Hello` MQTT service below. Remember to run a MQTT broker of your choice. ``` import os import sys # add context - your relative path from this example to py_hello_world dir e.g. like this sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) import apigear.mqtt import io_world.impl import io_world.mqtt import io_world.api async def main(): service = apigear.mqtt.Service("uniqueServiceIdForHelloService") source_io_world_hello = io_world.impl.Hello() serviceAdapter_io_world_hello = io_world.mqtt.HelloServiceAdapter(source_io_world_hello, service) await service.connect("localhost", 1883) # Set property, the change will be sent to all clients, and local handlers if any. local_last = io_world.api.Message(); local_last = "New message from server" source_io_world_hello.set_last(local_last); # Emit the signal, it will be sent to all clients signal_message = io_world.api.Message() signal_message = "New message from server" source_io_world_hello._just_said(signal_message) input("Press Enter to close") service.disconnect() if __name__ == '__main__': loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(main()) ``` note The implemented Mqtt Client (used both in apigear.mqtt.Client and apigear.mqtt.Service ) uses a thread to process network traffic. This mean, if you're having an asynchronous application, that reacting on the events inside you handlers (for all: properties, signals, operation results) may require using `loop.call_soon_threadsafe(callback, *args)` to handle the output in your main event loop. ### MQTT Messages[​](#mqtt-messages "Direct link to MQTT Messages") For the MQTT topic structure and payload format, see **[ApiGear over MQTT](/docs/protocols/mqtt/mapping.md)**. --- # Template Python This is the documentation for the *Python* template. It is split in these parts: * [Quick-Start](/template-python/docs/quickstart.md?current-template=template-python) is the easiest way to get started * [Features](/template-python/docs/features.md) explains the available code generator features, including what code is generated and why --- # Quick-Start The Quick-Start guide explains how to, in few steps, get from an API to a functional *Python* plugin. Steps 1 and 3 are universal for other technologies. In the step 2 you will choose a concrete *Python* template. For more general information about first steps with ApiGear [First Steps](/docs/guide/quick-start.md) The quick start enables only the basic features: the `api` generation and the `scaffold` skeleton with stub implementations you can fill in. For all available features check the [overview](/template-python/docs/features.md). ## 1. Install the Code Generator[​](#1-install-the-code-generator "Direct link to 1. Install the Code Generator") ApiGear provides two powerful tools: a Studio and a Command Line Interface (CLI). The Studio is a graphical tool for creating, editing API modules and projects, while the CLI is a command-line tool for generating code from API modules. Both offer comprehensive functionality, including SDK template management, API project creation, code generation, API monitoring, and API simulation. [Download Studio](https://github.com/apigear-io/studio/releases/latest) [Download CLI](https://github.com/apigear-io/cli/releases/latest) ### Install ApiGear Studio[​](#install-apigear-studio "Direct link to Install ApiGear Studio") ApiGear Studio is available for macOS, Windows, and Linux. You can download it from the [ApiGear Studio GitHub releases page](https://github.com/apigear-io/studio/releases/latest). For more detailed information on ApiGear Studio, refer to the dedicated [studio documentation](/docs/studio/intro.md). ### Install ApiGear CLI[​](#install-apigear-cli "Direct link to Install ApiGear CLI") The ApiGear CLI is a versatile command-line tool for generating code from API modules. It's available for macOS, Windows, and Linux. Download the latest version from the [ApiGear CLI GitHub releases page](https://github.com/apigear-io/cli/releases/latest). The CLI offers all the features available in the Studio. For more detailed information on the ApiGear CLI, consult the dedicated [CLI documentation](/docs/cli/intro.md). ## 2. Obtain the Template[​](#2-obtain-the-template "Direct link to 2. Obtain the Template") There are several methods to obtain the template: installation via the Studio, installation via the CLI, or cloning/downloading from GitHub. This step is optional The ApiGear Studio and the CLI detect the specified template in the [solution document](#solution-file) and install it automatically. Optional steps (click to expand) ### Installation via CLI[​](#installation-via-cli "Direct link to Installation via CLI") When using the CLI, only the highlighted line needs to be executed. You can verify the successful installation using the `template cache` command. * Unreal Engine * C++14 * Qt6 * Python ``` $ apigear template install apigear-io/template-unreal@v3.2.2 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-unreal@v3.2.2 | https://github.com/apigear-io/template-unreal.git | | v3.2.2 ... ``` ``` $ apigear template install apigear-io/template-cpp14@v3.6.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-cpp14@v3.6.0 | https://github.com/apigear-io/template-cpp14.git | | v3.6.0 ... ``` ``` $ apigear template install apigear-io/template-qtcpp@v0.4.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-qtcpp@v0.4.0 | https://github.com/apigear-io/template-qtcpp.git | | v0.4.0 ... ``` ``` $ apigear template install apigear-io/template-python@v1.0.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-python@v1.0.0 | https://github.com/apigear-io/template-python.git | | v1.0.0 ... ``` ### Installation via Studio[​](#installation-via-studio "Direct link to Installation via Studio") Installing the template through the Studio is straightforward: * Unreal Engine * C++14 * Qt6 * Python 1. Open an existing project or create a new one 2. Navigate to the `Templates` tab 3. Click `Install` on the `apigear-io/template-unreal` entry 1) Open an existing project or create a new one 2) Navigate to the `Templates` tab 3) Click `Install` on the `apigear-io/template-cpp14` entry 1. Open an existing project or create a new one 2. Navigate to the `Templates` tab 3. Click `Install` on the `apigear-io/template-qtcpp` entry 1) Open an existing project or create a new one 2) Navigate to the `Templates` tab 3) Click `Install` on the `apigear-io/template-python` entry ![Installing the template](/img/apigear-studio-install-unreal-template.png) Figure: Installing the template ### Cloning from GitHub[​](#cloning-from-github "Direct link to Cloning from GitHub") If you need to inspect or modify the template's source code, cloning or downloading the repository is recommended. The repository doesn't need to be part of your project and can be stored anywhere on your computer. * Unreal Engine * C++14 * Qt6 * Python ``` $ git clone https://github.com/apigear-io/template-unreal.git ``` ``` $ git clone https://github.com/apigear-io/template-cpp14.git ``` ``` $ git clone https://github.com/apigear-io/template-qtcpp.git ``` ``` $ git clone https://github.com/apigear-io/template-python.git ``` You can then configure the solution file to use your template by providing the relative path from the solution file to the template directory. ## 3. Set Up the Project[​](#3-set-up-the-project "Direct link to 3. Set Up the Project") A typical project requires two files: a solution file specifying the APIs and the template to use, and at least one API module file. Ideally, both should be placed in a folder named `apigear`, adjacent to each other. Alternatively, you can use the Studio to create a new project and modify the two example files provided. ### Solution File[​](#solution-file "Direct link to Solution File") Create a [solution](/docs/guide/quick-start.md#solution-file) file. The example below specifies: * Module files in *line 8*, here the `helloworld.module.yaml` module with the `Hello` API * The output directory for generated files in *line 9* * The template used to generate the code in *line 10* (this can also be a path to a local copy of the template) * The enabled features of the template in *line 13*, here the `stubs` feature, which provides a simple implementation of interfaces. - Unreal Engine - C++14 - Qt6 - Python helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: ue_docs inputs: - helloworld.module.yaml output: ../ue_docs template: apigear-io/template-unreal@v3.2.2 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: cpp_hello_world inputs: - helloworld.module.yaml output: ../cpp_hello_world template: apigear-io/template-cpp14@v3.6.0 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: qt_hello_world inputs: - helloworld.module.yaml output: ../qt_hello_world template: apigear-io/template-qtcpp@v0.4.0 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: hello_world inputs: - helloworld.module.yaml output: ../py_hello_world template: apigear-io/template-python@v1.0.0 force: true features: - stubs ``` Targets You can extend this solution file with additional targets, each for a different technology with a different template. The `module.yaml` file is technology-independent and can be used with any template. Template Version We highly recommend specifying the exact version of the template. Otherwise, a newer version will be automatically downloaded and used for code generation. This can lead to unexpected behavior if file structures or code changes in the template. note Set the `force` parameter to `true` if you want to always override all generated files. When set to `false`, some files, such as implementations (stub feature), won't be updated. API files are always updated regardless of this setting. ### API Module File[​](#api-module-file "Direct link to API Module File") Use your preferred text editor to create the `helloworld.module.yaml` file with the following example content: helloworld.module.yaml ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` ## 4. Generate Code[​](#4-generate-code "Direct link to 4. Generate Code") ### Generate via CLI[​](#generate-via-cli "Direct link to Generate via CLI") The following snippet demonstrates how to run the CLI and provides an example output: ``` $ apigear generate solution apigear/helloworld.solution.yaml 10:52:20 INF generated 21 files in 30ms. (20 write, 0 skip, 1 copy) topic=gen ``` * The `generate` command instructs the CLI to generate code * The `solution` parameter specifies that we want to process a solution file ### Generate via Studio[​](#generate-via-studio "Direct link to Generate via Studio") 1. Open the project 2. Navigate to the `Solutions` tab 3. Click `Run` on the `helloworld.solution.yaml` entry ![Generating code](/img/apigear-studio-generate-code.png) Figure: Generating code ## 5. Use the generated Python project[​](#5-use-the-generated-python-project "Direct link to 5. Use the generated Python project") ### Project folder structure[​](#project-folder-structure "Direct link to Project folder structure") With the output directory set as in example, both *ApiGear* files reside in an `apigear` subfolder next to the generated files. In this case the folder structure should look similar to this ``` 📂hello-world ┣ 📂apigear ┃ ┣ 📜helloworld.solution.yaml ┃ ┗ 📜helloworld.module.yaml ┣ 📂py_hello_world ┃ ┣ 📂io_world ┃ ┃ ┣ 📂api ┃ ┃ ┗ 📂impl ┃ ┣ 📂utils ┃ ┣ 📜Makefile ┃ ┣ 📜README.md ┃ ┗ 📜requirements.txt ``` Using the solution file from the previous paragraph the code will be generated in the `py_hello_world` folder. With subfolder for each module, here `io_world` as the name of module (defined in line 2 of `helloworld.module.yaml`). It contains both features generated: the `api` and the `scaffold` skeleton with stub implementations. note Make sure you have *Python* in at least 3.11 version and pip package installer for python. The generated code provides *Python* implementations. The following paragraphs show how you can use it. Start with installing all the requirements in `requirements.txt` file in top level directory with command `pip install --upgrade -r requirements.txt` tip It is recommended to install the dependencies in a virtual environment(venv). The `api.py` contains all definitions of the enums and structs for your module, as well as the abstract base classes for your interfaces. From now on you can simply import the `api` module or the scaffolded implementation modules and use them. For more details on the generated features see the [features overview](/template-python/docs/features.md). tip Check the extended features to see how to use your API over the network. note For simulation, see the [`olink` feature](/template-python/docs/features.md#extended), which provides a middle layer on your code side, and the [simulation docs](/docs/scripting/backends/intro.md). ### Create and run an example[​](#create-and-run-an-example "Direct link to Create and run an example") Prepare an `examples` folder in the `hello-world/py_hello_world` directory with a main. like this: ``` import asyncio import os import sys #add context - path to modules sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) import io_world.api import io_world.impl def main(): myHelloInstance = io_world.impl.Hello() # Try out properties: subscribe for changes def handle_last_changed(message): print("last property changed ") print(message) myHelloInstance.on_last_changed += handle_last_changed # and ask for change. messageForProperty = io_world.api.Message() messageForProperty.content = "New message"; myHelloInstance.set_last(messageForProperty); # Check the signals with subscribing for its change def handle_just_said(message): print("justSaid signal emitted ") print(message) myHelloInstance.on_just_said += handle_just_said # and emit one. messageForSignal = io_world.api.Message() messageForSignal.content = "Message from signal"; myHelloInstance._just_said(messageForSignal); # Play around executing operations, maybe they emit signals? or change the properties? method_result = myHelloInstance.say(io_world.api.Message(), io_world.api.When.NOW); print("Method result") print(method_result) myHelloInstance.on_last_changed -= handle_last_changed myHelloInstance.on_just_said -= handle_just_said if __name__ == '__main__': main() } ``` You can run it e.g from console. Open a terminal, navigate to generated code (`py_hello_world`) and run the example with command `python examples/example.py`. --- # Features This guide explains how to use the generated code, what are the available features and their benefits. ## Get started[​](#get-started "Direct link to Get started") This template generates code for [*Qt*](https://www.qt.io/) projects. In order to successfully compile and use the code, you need to have the *Qt Engine* installed (at least 5.12). Check [the Qt website](https://www.qt.io/download) for your options for open-source or commercial version. Basic understanding of *Qt* is required, and depending on your goal the qml or c++ knowledge is also necessary. ### Code generation[​](#code-generation "Direct link to Code generation") Follow the documentation for the [code generation](/docs/guide/quick-start.md) in general and [CLI](/docs/cli/generate.md) or the [Studio](/docs/studio/intro.md) tools. Or try first the [quick start guide](/template-qt6/docs/quickstart.md) which shows how to prepare api and generate code out of it. tip For questions regarding this template please go to our [discussions page](https://github.com/orgs/apigear-io/discussions). For feature requests or bug reports please use the [issue tracker](https://github.com/apigear-io/template-qtcpp/issues). ### Example API[​](#example-api "Direct link to Example API") The following code snippet contains the *API* which is used throughout this guide to demonstrate the generated code and its usage in *Qt*. Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` ## Features[​](#features-1 "Direct link to Features") ### Core[​](#core "Direct link to Core") Features generate a view model for the `api`. This can be used to implement a working *C++* and *qml* service and directly use it in your UI project. * [api](/template-qt6/docs/features/api.md) - generates compilable abstract base interface and a basic implementation for data types * [stubs](/template-qt6/docs/features/stubs.md) - adds a basic stubs for the `api`, you'll get classes that can actually be instantiated * [qmlplugin](/template-qt6/docs/features/qmlplugin.md) - generates plugin which can be used to add the qml types ### Extended[​](#extended "Direct link to Extended") Features can be used in combination with `api` and add more functionality on top, like the simulation * [olink](/template-qt6/docs/features/olink.md) - provides a client and server adapters for each interface, that can be connected to any of the other technology templates with support for [ObjectLink](/docs/protocols/objectlink/intro.md). Use this feature to connect with ApiGear simulation tools. * [monitor](/template-qt6/docs/features/monitor.md) - generates a middleware layer which logs all API events to the [CLI](/docs/cli/intro.md) or the [Studio](/docs/studio/intro.md) * [MQTT](/template-qt6/docs/features/mqtt.md) experimental - provides minimal working adapters for MQTT client and service side for each interfaces. Check also MQTT in other technology templates that supports it. * examples\_olink - generates: * `olinkserver` example with `main.cpp` that shows your services in olink server. * `olinkclient` example with `main.cpp` that shows your interfaces as olink client. * `qml` example with a `main.cpp` and `main.qml`. The qml uses your interfaces. The main cpp sets olink client factory (which provides olink client as a backend for the qml) and sets up the olink server with your services (normally you'd have separate app for server)'. * examples\_mqtt - generates: * `mqttserver` example with `main.cpp` that shows your services in mqtt server. * `mqttclient` example with `main.cpp` that shows your interfaces as mqtt client. * `mqttqml` example with a `main.cpp` and `main.qml`. The qml uses your interfaces. The main cpp sets mqtt client factory (which provides mqtt client as a backend for the qml) and sets up the mqtt server with your services (normally you'd have separate app for server)'.
Have in mind that generated code doesn't provide the mqtt broker. ![Features overview, including receiving data from network: Bottom floor shows possible inputs for your API, you can either obtain data from the network with OLink or MQTT or use local implementation. The top floor shows feature qmlpugin for qml oriented applications.](/img/features/featuresApp.png) Figure: Features overview, including receiving data from network: Bottom floor shows possible inputs for your API, you can either obtain data from the network with OLink or MQTT or use local implementation. The top floor shows feature qmlpugin for qml oriented applications. ![Features overview, including publishing data through network: Topmost floor shows your options for using your local implementation (bottom floor): you can use it in your local app and/or use method of sharing the data with clients in the network.](/img/features/featuresServer.png) Figure: Features overview, including publishing data through network: Topmost floor shows your options for using your local implementation (bottom floor): you can use it in your local app and/or use method of sharing the data with clients in the network. tip You may also use the OLink/Mqtt service side with Qml, you just need to provide implementation of IApiFactory, which will allow OLink/Mqtt Services Adapters use the network endpoints and the services implementation. There is also an *internal* feature `apigear`, which is generated for the *extended* features and is explained with them. Each feature can be selected using the solution file or via command line tool. note \*Features are case sensitive, make sure to always **use lower-case.** \* tip The *meta* feature `all` enables all specified features of the template. If you want to see the full extent of the generated code `all` is easiest solution. Please note, `all` is part of the code generator and not explicitly used within templates. ## Folder structure[​](#folder-structure "Direct link to Folder structure") This graph shows the full folder structure which is generated for `all` features enabled. Generated features are encapsulated in separate folders inside the module folder, here `io_world` or for the common features like `examples` and the internal helper feature `apigear`, a level above, in the `generation layer` level, here `qt_hello_world`. For more details visit the documentation for each feature. A `thirdparty` folder is contains Qt Promise (up to 5.x.x Qt version). ``` 📂hello-world ┣ 📂apigear ┃ ┣ 📜helloworld.solution.yaml ┃ ┗ 📜helloworld.module.yaml ┣ 📂qt_hello_world ┃ ┣ 📂apigear ┃ ┣ 📂examples ┃ ┃ ┣ 📂olinkclient ┃ ┃ ┣ 📂olinkserver ┃ ┃ ┣ 📂qml ┃ ┣ 📂io_world ┃ ┃ ┣ 📂api ┃ ┃ ┣ 📂implementation ┃ ┃ ┣ 📂monitor ┃ ┃ ┣ 📂olink ┃ ┃ ┗ 📂plugin ┃ ┣ 📂thirdparty ┃ ┗ 📜CMakeLists.txt ``` --- # API The feature `api` is the bare minimum for the code generation. What you receive is: * abstract `QObject` base class for each *interface* * *enums* * *structs* with a basic implementation note To use the your API in `QML` directly see the [plugin](/template-qt6/docs/features/qmlplugin.md) feature which adds QML wrappers and factory implementation for interfaces, factories for structures and creates a module plugin for the api types. ### File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With an example API Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure will be generated. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┣ 📂qt_hello_world ┃ ┣ 📂apigear ┃ ┣ 📂examples ┃ ┣ 📂io_world ┃ ┃ ┣ 📂api ┃ ┃ ┃ ┣ 📜api.cpp ┃ ┃ ┃ ┣ 📜api.h ┃ ┃ ┃ ┣ 📜iapifactory.h ┃ ┃ ┃ ┣ 📜json.adapter.h ┃ ┃ ┃ ┗ 📜CMakeLists.txt ... ``` ### Api[​](#api-1 "Direct link to Api") Files `📜api.h` and `📜api.cpp` contain the api: interfaces, data structures and enums. #### Enums[​](#enums "Direct link to Enums") Each enum is exposed to qml and their metatype is declared with `Q_DECLARE_METATYPE`. For convenience of use each enum has: * `toEnum` function converts given `quint8` to enum value. * `QDataSteram` in and out operators for the enum. #### Structs[​](#structs "Direct link to Structs") Each struct has its fields exposed for qml api.h ``` ... struct IO_WORLD_API_EXPORT Message { Q_GADGET Q_PROPERTY(QString content MEMBER m_content ) ... ``` note We generate the import/export statements (here IO\_WORLD\_API\_EXPORT) for all the classes and structs that have an implementation in a *cpp* file and may be used outside of the library. For convenience of use each structure has: * equality comparison operators * the `QDataSteram` in and out operators for the generated struct `Message`. The struct metatype is declared with `Q_DECLARE_METATYPE`. That may not be enough if you want to bind to fields of the struct in qml. In that case we suggest using a [plugin](/template-qt6/docs/features/qmlplugin.md) which provides factory for it. #### Interfaces[​](#interfaces "Direct link to Interfaces") Base interface version for Qt template is a QObject Abstract base class, here `AbstractHello`.
It contains: * a pure virtual `getters` and a `setters` for each property ``` virtual void setLast(const Message& last) = 0; virtual Message last() const = 0; ``` * pure virtual functions for each operation ``` virtual int say(const Message& msg, When::WhenEnum when) = 0; ``` * signals: for each api signal and for each api property - on property changed signal ``` signals: void justSaid(const Message& msg); void lastChanged(const Message& last); ``` ### Other[​](#other "Direct link to Other") `📜 CMakeLists.txt` for building this module, describing it's dependencies, and exposing built package for other features that will use it. `📜 json.adapter.h` prepares adapters for structs to easy read from or write to `nlohmann::json` ``` Message message = json.get(); nlohmann::json message = message; ``` `📜iapifactory.h` provides an interface for ApiFactory which can produce your interface instances, here the `AbstractHello` instance. It's purpose is to provide the chosen implementation to a QML wrappers. The factory main implementation is provided by the [qmlplugin](/template-qt6/docs/features/qmlplugin.md), which explains its purpose and usage in details. You can also find implementation of factory interface in [stubs](/template-qt6/docs/features/stubs.md), [monitor](/template-qt6/docs/features/monitor.md) and [olink](/template-qt6/docs/features/olink.md) features. They all allow creating different `AbstractHello` backend for QML wrapper. --- # Monitor Use our monitor feature to examine the interface calls, state and signals. With this feature you obtain a monitor client and a monitored version of your interfaces. The monitoring server is embedded into the [ApiGear Studio](/docs/studio/intro.md) and CLI application(/docs/cli/intro). More details on [monitoring](/docs/monitor/intro.md) ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With an example API Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure will be generated. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┃ ... ┣ 📂qt_hello_world ┃ ┣ 📂apigear ┃ ┃ ┣ 📂monitor ┃ ┃ ┃ ┣ 📜agentclient.cpp ┃ ┃ ┃ ┣ 📜agentclient.h ┃ ┃ ┃ ┗ 📜CMakeLists.txt ┃ ┃ ... ┃ ┣ 📂examples ┃ ┣ 📂io_world ┃ ┃ ┣ 📂api ┃ ┃ ┣ 📂implementation ┃ ┃ ┣ 📂olink ┃ ┃ ┣ 📂monitor ┃ ┃ ┃ ┣ 📜agent.cpp ┃ ┃ ┃ ┣ 📜agent.h ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┣ 📜tracedapifactory.cpp ┃ ┃ ┃ ┣ 📜tracedapifactory.h ┃ ┃ ┃ ┣ 📜hellotraced.cpp ┃ ┃ ┃ ┗ 📜hellotraced.h ... ``` ### Qt Trace Client[​](#qt-trace-client "Direct link to Qt Trace Client") When generating the monitor feature (or olink feature) you'll get an additional folder it the top most directory: 📂hello-world/📂apigear. The 📂monitor subfolder contains a client for logging, with a network layer (based on the QtWebSocket library). The class connects to the `Apigear Trace Server` and takes care of formatting and sending the traces. ### Monitor[​](#monitor-1 "Direct link to Monitor") Files `📜hellotraced.h` and `📜hellotraced.cpp` contain the trace wrapper for the `Hello` interface. The wrapper class is generated for each interface. ``` class IO_WORLD_MONITOR_EXPORT HelloTraced : public AbstractHello { explicit HelloTraced(std::shared_ptr impl); ... }; ``` It shares the ownership of the wrapped object. It forwards all calls to the implementation and forwards all the signals (including the property changed signals) calls from it, so you can use the `TracedHello` as a `Hello` object. Note that the `Hello` object is the object that fulfills the `AbstractHello` interface, it can be e.g. the [`OlinkClient`](/template-qt6/docs/features/olink.md) version of `Hello`. The `📜agent.h` and `📜agent.cpp` files contain helpers to prepare interface specific trace data for the general tracing [TraceClient](/template-qt6/docs/features/monitor.md#qt-trace-client) ### Tracing with QML objects[​](#tracing-with-qml-objects "Direct link to Tracing with QML objects") Files `📜tracedapifactory.h` and `📜tracedapifactory.cpp` contain the TracedApiFactory which creates the interfaces wrapped with the trace decorator. In our example it will produce only `Hello` interfaces: ``` class IO_WORLD_MONITOR_EXPORT TracedApiFactory : public QObject, public IApiFactory { TracedApiFactory(IApiFactory& factory, QObject *parent = nullptr); ``` The factory is necessary when you want to use the traced `Hello` object directly in QML. For that, you will choose the [QML version](/template-qt6/docs/features/qmlplugin.md#qml-wrappers) of your API to which you need provide proper backend - this is what the factory is for. See more details [on providing backend to QML wrapper](/template-qt6/docs/features/qmlplugin.md#providing-backend-to-qml-wrapper). The important difference between other factories implementations is, that the `TracedHello` is a wrapper, it requires the actual implementation to be passed to it. Therefor the factory needs a factory of the actual implementation objects, which it will wrap with the trace functionality. That it requires a factory in constructor. You can provide any backed you need e.g. your custom object, the [`OlinkClient`](/template-qt6/docs/features/olink.md). See the example of traced OlinkClient backend below: ``` // Prepare Factory before app is created. ApiGear::ObjectLink::ClientRegistry client_registry; ApiGear::ObjectLink::OLinkClient client(client_registry); io_world::OLinkFactory io_worldOlinkFactory(client); io_world::TracedApiFactory io_worldTracedOlinkFactory(io_worldOlinkFactory); io_world::ApiFactory::set(&io_worldTracedOlinkFactory); ... // Connect the client - all qml olink clients will be linked if the server services are already up. client.connectToHost(QUrl("ws://127.0.0.1:8182")); ``` The factory uses the `ApiGear::ObjectLink::OLinkClient` and links the objects when they are created. You don't have to perform any other actions, just use your qml version of `Hello`. Also on trace the TracedHello calls the `AgentClient` monitor class that handles connection to trace server. So with such setup you'll be able to get your data from `OlinkServer` and see the traces in ApiGear studio. ``` ... import io_world 1.0 ApplicationWindow { ... Button { width: 80 height: 80 text: qmlIoWorldHello.last.content onClicked: { qmlIoWorldHello.say(someMessage, someWhen) } } IoWorldHello { id: qmlIoWorldHello } ... } ``` ### Setup[​](#setup "Direct link to Setup") This feature requires using the classes wrapped with monitor decorators, which collect the traces, instead of using the implementation classes directly. Either in qml as shown above or in your code, where you create the interface instances. ``` #include "io_world/implementation/hello.h" #include "io_world/monitor/hellotraced.h" #include #include #include int main(){ auto ioWorldHello = std::make_shared(); io_world::HelloTraced ioWorldHelloTraced(ioWorldHello); useIoWorldHEllo(ioWorldHelloTraced); // your code that requires io_world::Hello } ``` Also make sure that you are using the same port for sending and receiving traces. For sending: check the [apigear monitor agent](/template-qt6/docs/features/monitor.md#qt-trace-client) `agentclient.cpp`, by default the address is set to `ws://localhost:8152/ws`. For receiver: go to studio settings and set same port number. --- # MQTT caution This is an experimental feature. It contains smallest working set of functionalities to adapt the generated interface for using over the network with MQTT protocol. It doesn't include the security. The error handling is minimal. It is not production ready. Please also check issues on github for this template. This feature purpose is not only to help you introduce MQTT protocol into your project, but also show that an existing protocol can be adapted for sharing your data in your ecosystem. When going through this document you may notice this implementation contains general client/server adapters in 📂hello-world/apigear/mqtt and an interface specific part generated from templates for each interface in 📂hello-world/qt\_hello\_world/io\_world/mqtt.

This feature provides a *client* and a *server* adapter for your interfaces for the MQTT protocol. It allows you to connect different applications in the same or different technologies (check all of our [templates](/docs/sdk/intro.md)).
Use an *Mqtt client* instead of your interface implementation to be able to receive data from remote service.
Use an *Mqtt server adapter* to expose your interface implementation as a remote service.
tip The MQTT broker is not provided with implementation. To be able to run client and service you need to run a broker of your choice. caution In this library we use STL classes in public interfaces. We assume that this library is built from source (configuration provided by generated CMakeLists). Otherwise, it has to be ensured that your build configuration matches the library build configuration. ### Before Start - prepare QMqtt Module[​](#before-start---prepare-qmqtt-module "Direct link to Before Start - prepare QMqtt Module") The mqtt library is not a part of standard Qt build. You need to install this module on your own, you can find it [here](https://github.com/qt/qtmqtt). Once you get the repository we recommend to build it with relWithDebug options e.g. like that ``` cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_CXX_FLAGS_DEBUG="-O2" . cmake --build . --config RelWithDebInfo cmake --install . --verbose ``` ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With an example API Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure will be generated. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┃ ... ┣ 📂qt_hello_world ┃ ┣ 📂apigear ┃ ┃ ┣ 📂monitor ┃ ┃ ┣ 📂mqtt ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┣ 📜mqttclient.cpp ┃ ┃ ┃ ┣ 📜mqttclient.h ┃ ┃ ┃ ┣ 📜mqttservice.cpp ┃ ┃ ┃ ┣ 📜mqttservice.h ┃ ┃ ┃ ┣ ... (helper files) ┃ ┃ ... ┃ ┣ 📂examples ┃ ┣ 📂io_world ┃ ┃ ┣ 📂api ┃ ┃ ┣ 📂implementation ┃ ┃ ┣ 📂monitor ┃ ┃ ┣ 📂mqtt ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┣ 📜mqtt_common.h ┃ ┃ ┃ ┣ 📜mqttfactory.cpp ┃ ┃ ┃ ┣ 📜mqttfactory.h ┃ ┃ ┃ ┣ 📜mqtthello.cpp ┃ ┃ ┃ ┣ 📜mqtthello.h ┃ ┃ ┃ ┣ 📜mqtthelloadapter.cpp ┃ ┃ ┃ ┗ 📜mqtthelloadapter.h ... ``` ### Qt apigear - The Network Layer[​](#qt-apigear---the-network-layer "Direct link to Qt apigear - The Network Layer") When generating the mqtt feature (or any of those: olink monitor feature) you'll get an additional folder it the top most directory: the 📂hello-world/📂apigear. The 📂mqtt subfolder contains objects that implement a network layer (based on Qt Mqtt library) for the MQTT protocol. Those are: * Client - Adapts the Qt MQTT client, to serve as an network endpoint for [interface client adapters](/template-qt6/docs/features/mqtt.md#mqtt-client-adapter). Exposes: * methods that allow receiving data for remote service: subscribing for properties changes, signals emission and method response invocation; * methods that allow remote using the service: requesting property change or invoking a method.
The client may serve many client interface adapters, even for the same interfaces (allows subscribing for same topic). In case many interface client adapters for same interface are connected: property changes and signals are provided to all interface client adapters, but the invoke method response will be delivered only for the one that requested it. * ServiceAdapter - Adapts the Qt MQTT client to serve as an network endpoint for [interface service adapters](/template-qt6/docs/features/mqtt.md#mqtt-server-adapter). Exposes: * methods that allow receiving requests from remote clients: subscribing for properties change requests, send method invocation; * methods that allow publishing property change, signal, functionality to handles sending a response for method invocation requests.
This ServiceAdapter may be used for many, interface service adapters, but it is not recommended to use more than one interface service adapter for the same interfaces. tip Have in mind that MQTT might not be suitable for high-frequency messages especially with one mqtt client serving more than one object. Also the brokers have limits for messages number/size queued from one client. In case you are not getting all the messages consider changing those or splitting traffic between more clients (maybe some handle the properties, some handle the methods). ### MQTT Client Adapter[​](#mqtt-client-adapter "Direct link to MQTT Client Adapter") Files `📜mqtthello.h` and `📜mqtthello.cpp` implement the remote client for the `Hello` interface - a `MqttHello` class.
The object is an `AbstractHello` implementation.
It requires an instance of Apigear::Mqtt::Client to work. It uses the Client to subscribe (and unsubscribe) for topics that allow receiving properties, signals and invoke responses from service. #### Properties[​](#properties "Direct link to Properties") The property getters (here getter `last`) return immediately the locally stored last received value from server.
The property setter (here setter `setLast ) requests setting a value on server side, local value is not changed.
You can connect to a property changed signals (here `void lastChanged(const Message& last)`) When the client receives information that server changed the property, a target property (here`last`) is updated locally and a signal that property has changed is emitted ( here `void lastChanged(const Message& last)\`. note The connected interface client adapter has its local properties in sync with a service. The properties messages are retained in mqtt broker, so all already set properties are provided. #### Operations[​](#operations "Direct link to Operations") The operations have additionally the async version, which is called by the immediate version.
The async version sends an invoke operation request to a server.
So calling `myMqttHelloInstance.say(myWhen)` implements execution of `sayAsync` and waits for the result (for non-void type of operations). Have in mind that this is a blocking operation. #### Signals[​](#signals "Direct link to Signals") You should not emit any signals from a client. You can connect to any signals offered by your interface (here `void justSaid(const Message& msg)` ) When a MqttHello client receives the message from server that indicates the signal was emitted it emits a signal (here `justSaid`). #### Use `MqttHello`[​](#use-mqtthello "Direct link to use-mqtthello") MqttHello is an adapter of QtMqtt (with protcol and network layer implementation), here provided by a `ApiGear::Mqtt::Client`. All you need to do is to pass the `ApiGear::Mqtt::Client` to your Interface Client Adapter, and request connecting to host when it is convenient for you. ``` // Create a client and make a connection ApiGear::Mqtt::Client client("UniqueClientName"); client.connectToHost("localhost", 1883); // Use the same port number as your broker is using. Typically, without any other settings it is "1883". // Create your service client. auto ioWorldHello = std::make_shared(client); // use your ioWorldHello as it was Hello implementation ioWorldHello->say(io_world::Message(), io_world::When::Now); auto lastMessage = ioWorldHello->last(); auto local_last = io_world::Message(); local_last.m_content = "new message"; ioWorldHello->setLast(local_last); ioWorldHello->connect(ioWorldHello.get(), &io_world::AbstractHello::justSaid, [](auto& param){qDebug()<< "received just said";}); ``` ### MQTT Server Adapter[​](#mqtt-server-adapter "Direct link to MQTT Server Adapter") Files `📜mqtthelloadapter.h` and `📜mqtthelloadapter.h` contain the mqtt server side adapter for the `Hello` interface - the `MqttHelloAdapter` class.
When creating the `MqttHelloAdapter` you need to provide the Apigear::Mqtt::ServiceAdapter and the local `AbstractHello` service object, you want to expose to clients.
`MqttHelloAdapter` object exposes the local object for remote usage with the MQTT protocol. It handles all the network requests, and calls your local object. The client connections and communication with them is handled transparently for you, no additional actions are needed. #### Properties[​](#properties-1 "Direct link to Properties") Each time a property changed signal (here `void lastChanged(const Message& last)` ) is emitted, a message is sent to all connected clients with information, which property changed to which value. This may occur either when you change a property directly on your local `Hello` object, or when a change property request message is received by the `MqttHelloAdapter`, which applies the property on your local `Hello` object. #### Operations[​](#operations-1 "Direct link to Operations") The operations invocation which came from the clients through the network will be performed on your local `Hello` object. The result of the operation (if any) will be returned only to a client, from which the message was send, not all clients. #### Signals[​](#signals-1 "Direct link to Signals") All the signals emitted by your local `Hello` objects are forwarded to all connected clients. #### Use `MqttHelloAdapter`[​](#use-mqtthelloadapter "Direct link to use-mqtthelloadapter") As mentioned earlier, this is an adapter of QtMqtt (with protcol and network layer implementation), here provided by a `ApiGear::Mqtt::ServiceAdapter` All you need to do is to pass this ServiceAdapter to your Interface Service Adapter, and request connecting to host when it is convenient for you. ``` // Prepare the Mqtt Service Adapter and an object which you want to expose. ApiGear::Mqtt::ServiceAdapter service("ServiceUniqueNameInMqtt"); service.connectToHost("localhost", 1883); // Use the same port number as your broker is using. Typically, without any other settings it is "1883". auto ioWorldHello = std::make_shared(); // Create your MqttHelloAdapter and add it to registry. auto ioWorldMqttHelloService = std::make_shared(service, ioWorldHello); // use your ioWorldHello implementation, all property changes, and signals will be passed to connected MqttHello clients. auto lastMessage = ioWorldHello->last(); ioWorldHello->say(lastMessage, io_world::When::Soon); io_world::Message someMessage; someMessage.m_content = "the new content"; ioWorldHello->setLast(someMessage); // after this call - if new property is different than current one - all clients will be informed about new value. emit ioWorldHello->justSaid(someMessage); ``` ### MQTT Factory - for QML usage[​](#mqtt-factory---for-qml-usage "Direct link to MQTT Factory - for QML usage") Files `📜mqttfactory.h` and `📜mqttfactory.cpp` contain the `MqttFactory` which creates the `MqttClient` version of your interfaces (returned as a`AbstractHello` interfaces).
The factory is necessary when you want to use the MqttClient implementation version for the [QML wrapper](/template-qt6/docs/features/qmlplugin.md#qml-wrappers). You need to set the `MqttFactory` as an API factory. See more details [on providing backend to QML wrapper](/template-qt6/docs/features/qmlplugin.md#providing-backend-to-qml-wrapper) ``` // Prepare Factory before app is created. ApiGear::Mqtt::Client client("UniqueClientForQmlExample"); io_world::MqttFactory io_worldMqttFactory(client); io_world::ApiFactory::set(&io_worldMqttFactory); ... client.connectToHost("localhost", 1883); ``` The factory uses the `ApiGear::ObjectLink::OLinkClient` and links the objects when they are created, you don't have to perform any other actions. ``` ... import io_world 1.0 ApplicationWindow { ... Button { width: 80 height: 80 text: qmlIoWorldHello.last.content onClicked: { qmlIoWorldHello.say(someMessage, someWhen) } } IoWorldHello { id: qmlIoWorldHello } ... } ``` ### MQTT Messages[​](#mqtt-messages "Direct link to MQTT Messages") For the MQTT topic structure and payload format, see **[ApiGear over MQTT](/docs/protocols/mqtt/mapping.md)**. --- # OLink This feature provides a *client* and a *server* adapter for your interfaces for the [ObjectLink](/docs/protocols/objectlink/intro.md) protocol. It allows you to connect different applications in the same or different technologies (check all of our [templates](/docs/sdk/intro.md)).
Use an *OLink client* instead of your interface implementation to connect to a remote service, or to a the [ApiGear simulation](/template-qt6/docs/features/olink.md#simulation). Use an *OLink server adapter* to expose your interface implementation as a remote service.
Below you'll find short introduction on `ApiGear ObjectLink` protocol. Before details on client and server, you'll also find piece of information on a network layer implementation for handling ObjectLink in `Qt`. note The `OLink client` and the `OLink server` objects are ready-to-use as they are, they don't' need any extra implementation. If you want quickly start working with them go to use sections of [client](/template-qt6/docs/features/olink.md#use-olinkhello), [server](/template-qt6/docs/features/olink.md#use-olinkhelloadapter) or [qml version](/template-qt6/docs/features/olink.md#olink-factory---for-qml-usage) ### Apigear ObjectLink protocol and ObjectLink core library[​](#apigear-objectlink-protocol-and-objectlink-core-library "Direct link to Apigear ObjectLink protocol and ObjectLink core library") The [ObjectLink](/docs/protocols/objectlink/intro.md) protocol is a lightweight protocol for the Objects described with an interface. It allows connecting a client object with a server object, and perform remote operations like: remote property change request (client) or notifications on property changed (server), inform about signal emission (server) and allows requesting a call of a method (client) and giving a response to the outcome (server). The Olink feature for your interface uses a library [ObjectLink core](https://github.com/apigear-io/objectlink-core-cpp), common for cpp based templates. The provided CMakeLists already contain all the dependencies, so you don't have to add it manually. The library provides an abstraction layer for handling the protocol, independent of the actual network stack. It encodes and decodes the messages and routes them to corresponding objects stored in its registry. The setup is minimal: instantiate the registries (for client and/or server) and pass them to network layer objects in [qt apigear library](/template-qt6/docs/features/olink.md#qt-apigear---the-network-layer) caution In this library we use STL classes in public interfaces. We assume that this library is built from source (configuration provided by generated CMakeLists). Otherwise, it has to be ensured that your build configuration matches the library build configuration. ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With an example API Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure will be generated. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┃ ... ┣ 📂qt_hello_world ┃ ┣ 📂apigear ┃ ┃ ┣ 📂monitor ┃ ┃ ┣ 📂olink ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┣ 📜olinkclient.cpp ┃ ┃ ┃ ┣ 📜olinkclient.h ┃ ┃ ┃ ┣ 📜olinkhost.cpp ┃ ┃ ┃ ┣ 📜olinkhost.h ┃ ┃ ┃ ┣ 📜olinkremote.cpp ┃ ┃ ┃ ┗ 📜olinkremote.h ┃ ┃ ... ┃ ┣ 📂examples ┃ ┣ 📂io_world ┃ ┃ ┣ 📂api ┃ ┃ ┣ 📂implementation ┃ ┃ ┣ 📂olink ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┣ 📜olinkfactory.cpp ┃ ┃ ┃ ┣ 📜olinkfactory.h ┃ ┃ ┃ ┣ 📜olinkhello.cpp ┃ ┃ ┃ ┣ 📜olinkhello.h ┃ ┃ ┃ ┣ 📜olinkhelloadapter.cpp ┃ ┃ ┃ ┗ 📜olinkhelloadapter.h ... ``` ### Qt apigear - The Network Layer[​](#qt-apigear---the-network-layer "Direct link to Qt apigear - The Network Layer") When generating the olink feature (or monitor feature) you'll get an additional folder it the top most directory: the 📂hello-world/📂apigear. The 📂olink subfolder contains objects that implement a network layer (based on QtWebSocket library) for the ObjectLink protocol. Those are: * OlinkClient - the client, that serves your [interface client adapters](/template-qt6/docs/features/olink.md#olink-client-adapter). The class is responsible for network connection for ObjectLink client side. It uses the ObjectLink core library for handling messages according to the ObjectLink protocol. Handles linking and unlinking with a remote service for the sink added by the user (the interface OlinkClient Adapters), with regard to the connection state. All the messages dedicated to your object will be delivered to the adapter and proper actions will be performed for the clients that use it, without any of your actions. Also the OlinkClient holds a message queue for messages during disconnected time. * OlinkHost and a helper class OlinkRemote- the server, which hosts your services exposed to network through the [interface server adapters](/template-qt6/docs/features/olink.md#olink-server-adapter). The class is responsible to set up the server and manage connections requested by clients. It uses ObjectLink core library for handling messages according to the ObjectLink protocol. All incoming messages will be translated to calls and routed to your local object. ### Olink Client Adapter[​](#olink-client-adapter "Direct link to Olink Client Adapter") Files `📜olinkhello.h` and `📜olinkhello.cpp` contain the olink client version of the `Hello` interface - an `OLinkHello` class.
It implements two interfaces: `AbstractHello` and a `IObjectSink`.
The IObjectSink interface comes from [ObjectLink core](https://github.com/apigear-io/objectlink-core-cpp): IObjectSink (click to expand) ``` class IObjectSink { public: virtual std::string olinkObjectName() = 0; virtual void olinkOnSignal(const std::string& signalId, const nlohmann::json& args) = 0; virtual void olinkOnPropertyChanged(const std::string& propertyId, const nlohmann::json& value) = 0; virtual void olinkOnInit(const std::string& objectId, const nlohmann::json& props, IClientNode* node) = 0; virtual void olinkOnRelease() = 0; }; ``` and is necessary for handling incoming messages from the server side and is used by a `OlinkClient` ([Qt Apigear Olink lib](/template-qt6/docs/features/olink.md#qt-apigear---the-network-layer)). Apart from setting up and tear down of the `OLinkHello` object, you don't need to perform any additional actions, the object `OLinkHello` will communicate with the server transparently and you should use it as an `AbstractHello` Object only. #### Properties[​](#properties "Direct link to Properties") The property getters (here `Message last()` ) return immediately the locally stored last received value from server.
The property setter (here `void setLast(const Message& last)` ) requests setting a value on server side, local value is not changed.
You can connect to a property changed signals (here `void lastChanged(const Message& last)` ) When the client receives information that server changed the property, a target property (here `last`) is updated locally and a signal that property has changed is emitted ( here `void lastChanged(const Message& last)`. note The connected client has its local properties initialized - on a successful linking client with server the client receives the init message, which contains current state of properties of the service on server side. #### Operations[​](#operations "Direct link to Operations") The operations have additionally the async version, which is called by the immediate version.
The async version sends an invoke operation request to a server.
So calling `myOlinkHelloInstance.say(myWhen)` implements execution of `sayAsync` and waits for the result (for non-void type of operations). Have in mind that this is a blocking operation. #### Signals[​](#signals "Direct link to Signals") You should not emit any signals from a client. You can connect to any signals offered by your interface (here `void justSaid(const Message& msg)` ) When a OlinkHello client receives the olinkOnSignal, a signal (here `justSaid`) is emitted. #### Use `OLinkHello`[​](#use-olinkhello "Direct link to use-olinkhello") As mentioned earlier you need a network layer, here provided by a `ApiGear::ObjectLink::OLinkClient` which also contains the protocol handling `ClientNode`. All you need to do is give it a global `ApiGear::ObjectLink::ClientRegistry`, request connecting to host when it is convenient for you and then add your `OlinkHello`. ``` // Create a global registry. ApiGear::ObjectLink::ClientRegistry registry; // Create a client and make a connection ApiGear::ObjectLink::OLinkClient client(registry); client.connectToHost(QUrl("ws://127.0.0.1:8182")); // Create your service client and request it linking, which will try to connect with a server side for this object. auto ioWorldHello = std::make_shared(); client.linkObjectSource(ioWorldHello); // use your ioWorldHello as it was Hello implementation ioWorldHello->say(io_world::Message(), io_world::When::Now); auto lastMessage = ioWorldHello->last(); auto local_last = io_world::Message(); local_last.m_content = "new message"; ioWorldHello->setLast(local_last); ioWorldHello->connect(ioWorldHello.get(), &io_world::AbstractHello::justSaid, [](auto& param){qDebug()<< "received just said";}); // remember to unlink your object if you won't use it anymore. client.unlinkObjectSource(ioWorldHello->olinkObjectName()); ``` caution ApiGear::ObjectLink::ClientRegistry can have only one object of a certain type. This means that, to have more than one 'OlinkHello' objects you need to put them in different registries. Each Object has an identifier which is created based on the Interface Name, this way client object can be matched with proper server object. ### Olink Server Adapter[​](#olink-server-adapter "Direct link to Olink Server Adapter") Files `📜olinkhelloadapter.h` and `📜olinkhelloadapter.h` contain the olink server adapter for the `Hello` interface - the `OLinkHelloAdapter` class.
It implements an `IObjectSource` interface (from [ObjectLink core](https://github.com/apigear-io/objectlink-core-cpp)), which wraps your `Hello` and exposes it for remote usage with the [ObjectLink](/docs/protocols/objectlink/intro.md) protocol. It handles all the network requests, and calls your local object.
When creating the `OLinkHelloAdapter` you need to provide the local `AbstractHello` service object, you want to expose to clients. The IObjectSource interface: IObjectSource (click to expand) ``` class IObjectSource { public: virtual std::string olinkObjectName() = 0; virtual nlohmann::json olinkInvoke(const std::string& methodId, const nlohmann::json& args) = 0; virtual void olinkSetProperty(const std::string& propertyId, const nlohmann::json& value) = 0; virtual void olinkLinked(const std::string& objectId, IRemoteNode* node) = 0; virtual void olinkUnlinked(const std::string& objectId) = 0; virtual nlohmann::json olinkCollectProperties() = 0; }; ``` After setting up the OlinkHost (Qt Apigear Olink lib) and registering your Interface registered, the client connections and communication with them is handled transparently for you, no additional actions are needed. note All the received messages from the clients are handled in a thread in which the connection resides, this means that your object may be used from different threads (local and each connection). #### Properties[​](#properties-1 "Direct link to Properties") Each time a property changed signal (here `void lastChanged(const Message& last)` ) is emitted, a message is sent to all connected clients with information, which property changed to which value. This may occur either when you change a property directly on your local `Hello` object, or when a change property request message is received by the `OLinkHelloAdapter`, which applies the property on your local `Hello` object. #### Operations[​](#operations-1 "Direct link to Operations") The operations invocation which came from the clients through the network will be performed on your local `Hello` object. The result of the operation (if any) will be returned only to a caller, not all clients. Have in mind that your object may be called from more that one thread. #### Signals[​](#signals-1 "Direct link to Signals") All the signals emitted by your local `Hello` objects are forwarded to all connected clients. #### Use `OLinkHelloAdapter`[​](#use-olinkhelloadapter "Direct link to use-olinkhelloadapter") As mentioned earlier you need a network layer, here provided by a `ApiGear::ObjectLink::OLinkHost` (The `ApiGear::ObjectLink::OLinkRemote` objects are created automatically per connection) which also contains the protocol handling `RemoteNodes`. All you need to do is give a global `ApiGear::ObjectLink::RemoteRegistry` with your `OLinkHelloAdapter` in it to `ApiGear::ObjectLink::OLinkHost`. ``` // Prepare the registry, the server, and an object which you want to expose. ApiGear::ObjectLink::RemoteRegistry registry; ApiGear::ObjectLink::OLinkHost server(registry); auto ioWorldHello = std::make_shared(); // Create your OLinkHelloAdapter and add it to registry. auto ioWorldOlinkHelloService = std::make_shared(registry, ioWorldHello.get()); registry.addSource(ioWorldOlinkHelloService); // Start server with source added to registry. server.listen("localhost", 8182); // use your ioWorldHello implementation, all property changes, and signals will be passed to connected OLink clients. auto lastMessage = ioWorldHello->last(); ioWorldHello->say(lastMessage, io_world::When::Soon); io_world::Message someMessage; someMessage.m_content = "the new content"; ioWorldHello->setLast(someMessage); // after this call - if new property is different than current one - all clients will be informed about new value. emit ioWorldHello->justSaid(someMessage); // Remember to remove your ioWorldOlinkHelloService after you finish using it. registry.removeSource(ioWorldOlinkHelloService->olinkObjectName()); ``` ### Olink Factory - for QML usage[​](#olink-factory---for-qml-usage "Direct link to Olink Factory - for QML usage") Files `📜olinkfactory.h` and `📜olinkfactory.cpp` contain the `OLinkFactory` which creates the `OlinkClient` version of your interfaces (returned as a`AbstractHello` interfaces).
The factory is necessary when you want to use the OlinkClient implementation version for the [QML wrapper](/template-qt6/docs/features/qmlplugin.md#qml-wrappers). You need to set the `OlinkFactory` as an API factory. See more details [on providing backend to QML wrapper](/template-qt6/docs/features/qmlplugin.md#providing-backend-to-qml-wrapper) ``` // Prepare Factory before app is created. ApiGear::ObjectLink::ClientRegistry client_registry; ApiGear::ObjectLink::OLinkClient client(client_registry); io_world::OLinkFactory io_worldOlinkFactory(client); io_world::ApiFactory::set(&io_worldOlinkFactory); ... // Connect the client - all qml olink clients will be linked if the server services are already up. client.connectToHost(QUrl("ws://127.0.0.1:8182")); ``` The factory uses the `ApiGear::ObjectLink::OLinkClient` and links the objects when they are created, you don't have to perform any other actions. ``` ... import io_world 1.0 ApplicationWindow { ... Button { width: 80 height: 80 text: qmlIoWorldHello.last.content onClicked: { qmlIoWorldHello.say(someMessage, someWhen) } } IoWorldHello { id: qmlIoWorldHello } ... } ``` ## Simulation[​](#simulation "Direct link to Simulation") The simulation can be used to test, demonstrate or develop applications without the need to have the actual service available. The simulation server is integrated into the [ApiGear studio](/docs/studio/intro.md) and the [CLI](/docs/cli/simulate.md). For simulating you will use [simulation scenarios](/docs/scripting/backends/scenario.md) They allow to define sequences of actions. The actions can change the property values of the service or emit signals. The scenarios can be written using a YAML schema. See more on [simulation](/docs/scripting/backends/intro.md). ### Prepare your application[​](#prepare-your-application "Direct link to Prepare your application") Make sure you are using OlinkClient as your `Hello` object, either: * instance on [cpp side](/template-qt6/docs/features/olink.md#olink-client-adapter) * or [qml version](/template-qt6/docs/features/olink.md#olink-factory---for-qml-usage) and connect to the same url and port that you configure in the ApiGear studio. You may try our example - copy the code into new folder in generated `examples` folder This is an example with qml version of using OlinkClient. ##### main.cpp[​](#maincpp "Direct link to main.cpp") The main.cpp sets up the qml factory main.cpp (click to expand) ``` #include "io_world/api/apifactory.h" #include "io_world/olink/olinkfactory.h" #include "io_world/monitor/tracedapifactory.h" #include #include "olink/clientregistry.h" #include #include #include "olink/olinkclient.h" #include #include #include int main(int argc, char *argv[]){ // Prepare Factory before app is created. ApiGear::ObjectLink::ClientRegistry client_registry; ApiGear::ObjectLink::OLinkClient client(client_registry); io_world::OLinkFactory io_worldOlinkFactory(client); io_world::ApiFactory::set(&io_worldOlinkFactory); // Create main app const QUrl url(QStringLiteral("qrc:/main.qml")); QGuiApplication app(argc, argv); QQmlApplicationEngine engine; engine.load(url); // Connect the client - all qml olink clients will be linked client.connectToHost(QUrl("ws://127.0.0.1:8182/ws")); auto result = app.exec(); return result; } ``` note Make sure to configure the client to use the correct IP and port configuration of your simulation server e.g. set `client.connectToHost(QUrl("ws://127.0.0.1:8182/ws"));` ##### main.qml[​](#mainqml "Direct link to main.qml") As a starting point, describes 3 buttons, which are connected to: 1. Usage of method - on click, it executes the method (request to server) and shows the outcome as a button text. 2. Binding to a property - text of button 2 3. Binding to a signal outcome - text of button 3 with a following code main.qml(click to expand) ``` import QtQuick 2.15 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.15 import io.world 1.0 import io.world.MessageFactorySingleton 1.0 ApplicationWindow { id: appWindow visible: true width: 300 height: 250 ColumnLayout { spacing: 10 id: mainLayout anchors.fill: parent Button { id: button1 Layout.alignment: Qt.AlignCenter background: Rectangle { implicitWidth: 250 implicitHeight: 50 radius: 4 color: "#9ed545" } text: "Click to get result of a method" onClicked: { var msg = IoWorldMessageFactory.create() msg.content = "some message" qmlIoWorldHello.say(msg, IoWorldWhen.New) } } Button { id: button2 Layout.alignment: Qt.AlignCenter background: Rectangle { implicitWidth: 250 implicitHeight: 50 radius: 4 color: "#9ed545" } text: qmlIoWorldHello.last.content onClicked: { console.log("Or events like changing properties or invoking methods") } } Button { id: button3 Layout.alignment: Qt.AlignCenter background: Rectangle { implicitWidth: 250 implicitHeight: 50 radius: 4 color: "#9ed545" } text: "Will show singal message" } } IoWorldHello { id: qmlIoWorldHello onJustSaid:{ console.log("signal received") button3.text = msg.content } } } ``` you should get an app like this ![Testing application used with the simulation.](/img/qtSimuApp.png) Figure: Testing application used with the simulation. ##### CMakeLists.txt and resource.qrc[​](#cmakeliststxt-and-resourceqrc "Direct link to CMakeLists.txt and resource.qrc") CMakeLists.txt(click to expand) ``` project(QmlExamlple) cmake_minimum_required(VERSION 3.20) # append local binary directory for conan packages to be found set(CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR} ${CMAKE_MODULE_PATH}) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set (SOURCES main.cpp main.qml resources.qrc ) set(QML_IMPORT_PATH "${CMAKE_BINARY_DIR}/imports" CACHE STRING "" FORCE) set(QML2_IMPORT_PATH "${CMAKE_BINARY_DIR}/imports" CACHE STRING "" FORCE) add_executable(QmlExamlple ${SOURCES} ) find_package(Qt5 REQUIRED COMPONENTS Core Qml Network WebSockets Gui Quick QuickControls2 QuickWidgets) find_package(apigear QUIET COMPONENTS olink_qt) find_package(apigear QUIET COMPONENTS olink_core) if(NOT olink_core_FOUND) # pull objectlink-core-cpp as dependency message(STATUS "objectlink-core-cpp NOT FOUND, fetching the git repository") FetchContent_Declare(olink_core GIT_REPOSITORY https://github.com/apigear-io/objectlink-core-cpp.git GIT_TAG v0.2.4 GIT_SHALLOW TRUE EXCLUDE_FROM_ALL FALSE ) FetchContent_MakeAvailable(olink_core) endif() find_package(io_world QUIET COMPONENTS io_world_api io_world_impl io_world_olink plugin_io_world io_world_monitor) target_link_libraries(QmlExamlple io_world_api io_world_impl io_world_olink plugin_io_world io_world_monitor Qt5::Core Qt5::Qml Qt5::WebSockets Qt5::Gui Qt5::Quick Qt5::QuickControls2 Qt5::QuickWidgets olink_qt olink_core ) install(TARGETS QmlExamlple RUNTIME DESTINATION bin COMPONENT Runtime) ``` resources.qrc(click to expand) ``` main.qml ``` ### Prepare the scenario[​](#prepare-the-scenario "Direct link to Prepare the scenario") As a starting point you can check our simulation scenario: Scenario(click to expand) ``` schema: apigear.scenario/1.0 name: "first scenario" version: "1.0.0" #initial properties and setting gunction response interfaces: - name: io.world.Hello #( module io.world and interface Hello combination) properties: last: {content: "Initial"} operations: - name: say actions: - $return: { value: 88 } # sequence of changing properties and emitting signals sequences: - name: play with hello interval: 2000 # 2 seconds interface: io.world.Hello loops: 3 steps: # step is called every 2 secs according to interval - name: change property actions: - $set: { last: {content: "First Change of Property"} } - name: emit signal actions: - $signal: { justSaid: [ {content: "First Message"} ] } - name: change property AND emit signal actions: - $set: { last: {content: "Second Change of Property"} } - $signal: { justSaid: [ {content: "Other Signal"} ] } ``` Apart from the header with the scenario name and its version it consists of two parts: * initialization - for each interface (here, our ui.world.Hello line 6), we're setting initial values of properties(line 8) and the return value of function calls (line 12) * sequence of events. You can prepare more than one sequence. For each of them set interval between calling each action (line 16), set how many runs of that sequence you want to execute (line 18) and name the interface the action should be applied to (line 17). In each action you may change many properties, and emit signals. ### Run the simulation[​](#run-the-simulation "Direct link to Run the simulation") Put your scenario file in the `📂hello-world/apigear` along with other `.yaml` files. If you are using the studio, reload your project. Go to `settings` and make sure to set same the port as in your client app (here `8182`). Click the `play` button in the application in the `simulation` tab to start the simulation scenario, then start the client application. Instead of the Apigear Studio application can also use its console version, run your scenario with command: ``` apigear simulate run path-from-cli-to-scenario/helloworldtest.scenario.yaml --addr :8182 ``` --- # QML Plugin With this feature you can use a qml version of your interface as a \[Qml plugin]\(. It provides qml -available version of you interfaces and factory implementation for interfaces and factories for structures. It creates a qml module for your interface. ### File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With an example API Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure will be generated. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┣ 📂qt_hello_world ┃ ┣ 📂apigear ┃ ┣ 📂examples ┃ ┣ 📂io_world ┃ ┃ ┣ 📂api ┃ ┃ ... ┃ ┃ ┣ 📂qmlplugin ┃ ┃ ┃ ┣ 📜apifactory.cpp ┃ ┃ ┃ ┣ 📜apifactory.h ┃ ┃ ┃ ┗ 📜CMakeLists.txt ┃ ┃ ┃ ┣ 📜qml_api.cpp ┃ ┃ ┃ ┣ 📜qml_api.h ┃ ┃ ┃ ┣ 📜qmlhello.cpp ┃ ┃ ┃ ┣ 📜qmlhello.h ``` ### QML API[​](#qml-api "Direct link to QML API") To avoid possible namespace collision between different modules we use `QML_NAMED_ELEMENT()` with module name and element name for all registering objects, like e.g. `QML_NAMED_ELEMENT(IoWorldWhen)` for `When` enum. Files `📜qml_api.h` and `📜qml_api.cpp` contain: * the enum wrapper for enum registration, which adds only QML macros for registering enum from `api` library. ``` struct ForeignWhen : public QObject { Q_OBJECT QML_FOREIGN(When) QML_NAMED_ELEMENT(IoWorldWhen) QML_UNCREATABLE("This is enum class") QML_ADDED_IN_VERSION(1,0) }; ``` * the factory for uncreatable otherwise in QML structures ``` class IO_WORLD_QML_EXPORT MessageFactory : public QObject { Q_OBJECT QML_ADDED_IN_VERSION(1,0) QML_NAMED_ELEMENT(IoWorldMessageFactory) QML_SINGLETON public: Q_INVOKABLE io_world::Message create(QString in_content); }; ``` All factories are `QML_SINGLETONS`, so you don't have to create any instance of it, just call `var myInstance = IoWorldMessageFactory.create(...)` in your qml code. The create function requires input parameters for all members of the created structure. note The qml plugin uses the version information form helloworld.module.yaml to register types, both with the `QML_ADDED_IN_VERSION` and with `qt_add_qml_module` in CMakeLists.txt. Use this version when importing your module in qml, here `import io_world 1.0` ### QML Wrappers[​](#qml-wrappers "Direct link to QML Wrappers") Files `📜qmlhello.h` and `📜qmlhello.cpp` contain a qml wrapper for the `Hello` interface.
The qml wrapper requires providing an implemented `AbstractHello` instance. For that we use the factory described in \[api] module with `📜iapifactory.h` and implemented in this module in (`📜apifactory.h`, `📜apifactory.cpp`). The functionality is explained [below](/template-qt6/docs/features/qmlplugin.md#providing-backend-to-qml-wrapper). The QML wrapper (simplified version) qmlhello.h ``` class IO_WORLD_QML_EXPORT QMLHello : public AbstractHello { Q_OBJECT QML_NAMED_ELEMENT(IoWorldHello) ... Q_PROPERTY(Message last READ last WRITE setLast NOTIFY lastChanged) ... Q_INVOKABLE int say(const Message& msg, When::WhenEnum when) override; ... Q_SIGNALS: void lastChanged(const Message& last); } ``` The qml wrapper makes the interface usable from qml:
Line 6 shows exposed properties, with emitting signal on property changed from qml (`NOTIFY lastChanged`) - hence overriding signal for property changed (line 11).
Line 8 shows the function exposed as a callable from qml.
The wrapper forwards the calls to backend and all the signals from backend to qml wrapper. caution The signals defined in the api shall be emitted by the backend and handled in qml, not the other way around. When the signal is emitted in qml, it won't reach the backend implementation. To use QMLHello you just need to create an instance of it in your QML code with a proper name (the one used in `QML_NAMED_ELEMENT`): ``` import QtQuick 2.15 import QtQuick.Layouts 1.2 import io_world 1.0 ApplicationWindow { id: appWindow visible: true width: 300 height: 300 IoWorldHello { id: qmlIoWorldHello } ``` ### Providing backend to QML Wrapper.[​](#providing-backend-to-qml-wrapper "Direct link to Providing backend to QML Wrapper.") The `QmlHello` is instantiated in qml without any handle to get or set the backend. Therefore, the global `ApiFactory` class of `IApiFactory` type is added to provide a flexible and extensible way to create it. The `QmlHello` class creates the `Hello` backend object by itself using the global `ApiFactory`. By providing your `IApiFactory` implementation, you will be able to use a `Hello` backend of your choice. Here is an example code snippet that shows how to prepare your `IApiFactory` io\_world/customfactory.h ``` #pragma once #include "io_world/api/iapifactory.h" // #include "your CustomHello header file" #include class CustomFactory : public QObject, public IApiFactory { public: CustomFactory(..., QObject *parent = nullptr); //provide any extra resources your factory needs. std::shared_ptr createHello(QObject *parent = nullptr) override { return make_shared(...); // provide any resource your CustomHello needs. } }; ``` and set in code main.cpp ``` #include "io_world/api/apifactory.h" #include "io_world/customfactory.h" #include #include #include int main(int argc, char *argv[]){ io_world::CustomFactory io_worldFactory(); // Setting the CustomFactory as a global factory. From now, each qml object will create backend with it. io_world::ApiFactory::set(&io_worldFactory); // Starting your application after setting the factory. const QUrl url(QStringLiteral("qrc:/main.qml")); QGuiApplication app(argc, argv); QQmlApplicationEngine engine; engine.load(url); ... ``` this way, on every `QmlHello` instantiation, your `CustomFactory::createHello` is called, and the returned object is used by `QmlHello`. note You can check the `qml example` or factories provided by other features. For instance [olink](/template-qt6/docs/features/olink.md). caution Your IApiFactory implementation needs to be set to `ApiFactory` once, before the QML file is loaded. ### Paths[​](#paths "Direct link to Paths") If you run your application outside of *Qt Creator* you need to add the import paths (paths where the plugins are) to runtime. You can either set the QML\_IMPORT\_PATH and QML2\_IMPORT\_PATH environment variables - for ad-hoc import paths (for debugging or testing) or use the `QQmlEngine::addImportPath()` function for fixed import paths that should always be available. ``` QGuiApplication app(argc, argv); QQmlApplicationEngine engine; app.addLibraryPath(the-directory); ``` Usually the plugins are located in your `CMAKE_BINARY_DIR` in the target folder. In our example code that would be "your-build-dir/io/world/qmlplugin" folder. Read more on [import paths](https://doc.qt.io/qt-6/qtqml-syntax-imports.html#qml-import-path) --- # Stubs The feature `stubs` adds: * a semi-featured starting point for your implementation a instantiable classes for your Interfaces. * the factory, that produces this implementation stubs (for QMLWrapper) * a setup for tests. ### File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With an example API Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure will be generated. The purpose and content of each file is explained below. ``` 📂hello-world ┣ 📂apigear ┣ 📂qt_hello_world ┃ ┣ 📂apigear ┃ ┣ 📂examples ┃ ┣ 📂io_world ┃ ┃ ┣ 📂api ┃ ┃ ┣ 📂implementation ┃ ┃ ┃ ┣ 📂tests ┃ ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┃ ┣ 📜test_hello.cpp ┃ ┃ ┃ ┃ ┗ 📜test_hello.h ┃ ┃ ┃ ┣ 📜CMakeLists.txt ┃ ┃ ┃ ┣ 📜factory.cpp ┃ ┃ ┃ ┣ 📜factory.h ┃ ┃ ┃ ┣ 📜hello.cpp ┃ ┃ ┃ ┗ 📜hello.h ... ``` ### The Stub[​](#the-stub "Direct link to The Stub") Files `📜hello.h` and `📜hello.cpp` contain the implementation of the `AbstractHello`. The class skeleton: * adds the properties of the interface as a private class members - here `Message m_last` * initializes them with default values in ctor. * defines getters and setters for each property * provides empty implementation of operation to implement the business logic. note We generate the import/export statements - here IO\_WORLD\_IMPL\_EXPORT - for all the classes and structs that have an implementation in a *cpp* file and may be used outside of the library. ### Factory[​](#factory "Direct link to Factory") Factory of stub is defined in `📜factory.h` and `📜factory.cpp` This factory is necessary, when you want to provide this stub implementation to the QML Wrapper. See [this](/template-qt6/docs/features/qmlplugin.md#providing-backend-to-qml-wrapper) api section for full information. Also have in mind, that using this version of provided pair "factory - stub", you will not have any handle to interact with a created stub from code. Only changes and interaction will be possible from QML. You could create your version of a factory which also registers the objects to some kind of global registry, which you're able to reach from code. ### Test[​](#test "Direct link to Test") For each interface we provide a template for your tests: pair of `📜test_hello.h` and `📜test_hello.cpp` files with a test class for the interface. It contains 3 kinds of init methods: before each test cases is executed, before first test case is executed and one to create a global data table for tests; For clean up there are 2 methods: one called after each test and one called after last test case is called. There are also slots - initial dummy tests, one for each operation and property. --- # Template Qt6 This is the documentation for the *Qt6* template. It is split in these parts: * [Quick-Start](/template-qt6/docs/quickstart.md?current-template=template-qtcpp) is the easiest way to get started * [Features](/template-qt6/docs/features.md) explains the available code generator features, including what code is generated and why --- # Quick-Start The Quick-Start guide explains how to, in few steps, get from an API to a functional *Qt* plugin. Steps 1 and 3 are universal for other technologies. In the step 2 you will choose a concrete *Qt* template. For more general information about first steps with ApiGear [First Steps](/docs/guide/quick-start.md) The quick start enables only basic features: the [api](/template-qt6/docs/features/api.md) generation and simple [stub](/template-qt6/docs/features/stubs.md) implementation. For all available features check the [overview](/template-qt6/docs/features.md). ## 1. Install the Code Generator[​](#1-install-the-code-generator "Direct link to 1. Install the Code Generator") ApiGear provides two powerful tools: a Studio and a Command Line Interface (CLI). The Studio is a graphical tool for creating, editing API modules and projects, while the CLI is a command-line tool for generating code from API modules. Both offer comprehensive functionality, including SDK template management, API project creation, code generation, API monitoring, and API simulation. [Download Studio](https://github.com/apigear-io/studio/releases/latest) [Download CLI](https://github.com/apigear-io/cli/releases/latest) ### Install ApiGear Studio[​](#install-apigear-studio "Direct link to Install ApiGear Studio") ApiGear Studio is available for macOS, Windows, and Linux. You can download it from the [ApiGear Studio GitHub releases page](https://github.com/apigear-io/studio/releases/latest). For more detailed information on ApiGear Studio, refer to the dedicated [studio documentation](/docs/studio/intro.md). ### Install ApiGear CLI[​](#install-apigear-cli "Direct link to Install ApiGear CLI") The ApiGear CLI is a versatile command-line tool for generating code from API modules. It's available for macOS, Windows, and Linux. Download the latest version from the [ApiGear CLI GitHub releases page](https://github.com/apigear-io/cli/releases/latest). The CLI offers all the features available in the Studio. For more detailed information on the ApiGear CLI, consult the dedicated [CLI documentation](/docs/cli/intro.md). ## 2. Obtain the Template[​](#2-obtain-the-template "Direct link to 2. Obtain the Template") There are several methods to obtain the template: installation via the Studio, installation via the CLI, or cloning/downloading from GitHub. This step is optional The ApiGear Studio and the CLI detect the specified template in the [solution document](#solution-file) and install it automatically. Optional steps (click to expand) ### Installation via CLI[​](#installation-via-cli "Direct link to Installation via CLI") When using the CLI, only the highlighted line needs to be executed. You can verify the successful installation using the `template cache` command. * Unreal Engine * C++14 * Qt6 * Python ``` $ apigear template install apigear-io/template-unreal@v3.2.2 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-unreal@v3.2.2 | https://github.com/apigear-io/template-unreal.git | | v3.2.2 ... ``` ``` $ apigear template install apigear-io/template-cpp14@v3.6.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-cpp14@v3.6.0 | https://github.com/apigear-io/template-cpp14.git | | v3.6.0 ... ``` ``` $ apigear template install apigear-io/template-qtcpp@v0.4.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-qtcpp@v0.4.0 | https://github.com/apigear-io/template-qtcpp.git | | v0.4.0 ... ``` ``` $ apigear template install apigear-io/template-python@v1.0.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-python@v1.0.0 | https://github.com/apigear-io/template-python.git | | v1.0.0 ... ``` ### Installation via Studio[​](#installation-via-studio "Direct link to Installation via Studio") Installing the template through the Studio is straightforward: * Unreal Engine * C++14 * Qt6 * Python 1. Open an existing project or create a new one 2. Navigate to the `Templates` tab 3. Click `Install` on the `apigear-io/template-unreal` entry 1) Open an existing project or create a new one 2) Navigate to the `Templates` tab 3) Click `Install` on the `apigear-io/template-cpp14` entry 1. Open an existing project or create a new one 2. Navigate to the `Templates` tab 3. Click `Install` on the `apigear-io/template-qtcpp` entry 1) Open an existing project or create a new one 2) Navigate to the `Templates` tab 3) Click `Install` on the `apigear-io/template-python` entry ![Installing the template](/img/apigear-studio-install-unreal-template.png) Figure: Installing the template ### Cloning from GitHub[​](#cloning-from-github "Direct link to Cloning from GitHub") If you need to inspect or modify the template's source code, cloning or downloading the repository is recommended. The repository doesn't need to be part of your project and can be stored anywhere on your computer. * Unreal Engine * C++14 * Qt6 * Python ``` $ git clone https://github.com/apigear-io/template-unreal.git ``` ``` $ git clone https://github.com/apigear-io/template-cpp14.git ``` ``` $ git clone https://github.com/apigear-io/template-qtcpp.git ``` ``` $ git clone https://github.com/apigear-io/template-python.git ``` You can then configure the solution file to use your template by providing the relative path from the solution file to the template directory. ## 3. Set Up the Project[​](#3-set-up-the-project "Direct link to 3. Set Up the Project") A typical project requires two files: a solution file specifying the APIs and the template to use, and at least one API module file. Ideally, both should be placed in a folder named `apigear`, adjacent to each other. Alternatively, you can use the Studio to create a new project and modify the two example files provided. ### Solution File[​](#solution-file "Direct link to Solution File") Create a [solution](/docs/guide/quick-start.md#solution-file) file. The example below specifies: * Module files in *line 8*, here the `helloworld.module.yaml` module with the `Hello` API * The output directory for generated files in *line 9* * The template used to generate the code in *line 10* (this can also be a path to a local copy of the template) * The enabled features of the template in *line 13*, here the `stubs` feature, which provides a simple implementation of interfaces. - Unreal Engine - C++14 - Qt6 - Python helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: ue_docs inputs: - helloworld.module.yaml output: ../ue_docs template: apigear-io/template-unreal@v3.2.2 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: cpp_hello_world inputs: - helloworld.module.yaml output: ../cpp_hello_world template: apigear-io/template-cpp14@v3.6.0 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: qt_hello_world inputs: - helloworld.module.yaml output: ../qt_hello_world template: apigear-io/template-qtcpp@v0.4.0 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: hello_world inputs: - helloworld.module.yaml output: ../py_hello_world template: apigear-io/template-python@v1.0.0 force: true features: - stubs ``` Targets You can extend this solution file with additional targets, each for a different technology with a different template. The `module.yaml` file is technology-independent and can be used with any template. Template Version We highly recommend specifying the exact version of the template. Otherwise, a newer version will be automatically downloaded and used for code generation. This can lead to unexpected behavior if file structures or code changes in the template. note Set the `force` parameter to `true` if you want to always override all generated files. When set to `false`, some files, such as implementations (stub feature), won't be updated. API files are always updated regardless of this setting. ### API Module File[​](#api-module-file "Direct link to API Module File") Use your preferred text editor to create the `helloworld.module.yaml` file with the following example content: helloworld.module.yaml ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` ## 4. Generate Code[​](#4-generate-code "Direct link to 4. Generate Code") ### Generate via CLI[​](#generate-via-cli "Direct link to Generate via CLI") The following snippet demonstrates how to run the CLI and provides an example output: ``` $ apigear generate solution apigear/helloworld.solution.yaml 10:52:20 INF generated 21 files in 30ms. (20 write, 0 skip, 1 copy) topic=gen ``` * The `generate` command instructs the CLI to generate code * The `solution` parameter specifies that we want to process a solution file ### Generate via Studio[​](#generate-via-studio "Direct link to Generate via Studio") 1. Open the project 2. Navigate to the `Solutions` tab 3. Click `Run` on the `helloworld.solution.yaml` entry ![Generating code](/img/apigear-studio-generate-code.png) Figure: Generating code ## 5. Use the generated Qt project[​](#5-use-the-generated-qt-project "Direct link to 5. Use the generated Qt project") ### Project folder structure[​](#project-folder-structure "Direct link to Project folder structure") With the output directory set as in example, both *ApiGear* files reside in an `apigear` subfolder next to the *Qt* generated files. In this case the folder structure should look similar to this ``` 📂hello-world ┣ 📂apigear ┃ ┣ 📜helloworld.solution.yaml ┃ ┗ 📜helloworld.module.yaml ┣ 📂qt_hello_world ┃ ┣ 📂io_world ┃ ┃ ┣ 📂api ┃ ┃ ┗ 📂implementation ┃ ┗ 📜CMakeLists.txt ``` Using the solution file from the previous paragraph the code will be generated in the `qt_hello_world` folder. With subfolder for each module, here `io_world` as the name of module (defined in line 2 of `helloworld.module.yaml`). It contains both features generated: a basic api and a stub implementation. The generated code provides Qt *C++* implementations. The following paragraphs show how you can use it. You can start your project loading the top level CMakeLists.txt in `qt_hello_world` folder. The 'api.h' contains all definitions of the enums and structs for your module, as well as the QObject abstract base classes for your Interfaces. From now on you can simply include the header files for the api interface or the stub implementation and use it. For more details on generated features please check [api](/template-qt6/docs/features/api.md), [stubs](/template-qt6/docs/features/stubs.md). tip Check the "example" and [qmlplugin](/template-qt6/docs/features/qmlplugin.md) features to see how to use your API directly in qml with the generated wrappers. note For the pure virtual interface see the [pure C++ template](/template-cpp14/docs/features/api.md#interfaces) note For the simulation check [the olink feature](/template-qt6/docs/features/olink.md) which provides middle layer on your code side and the [simulation](/docs/scripting/backends/intro.md) explained. ### Create and run an example[​](#create-and-run-an-example "Direct link to Create and run an example") Prepare an `examples` folder in the `hello-world/qt_hello_world` directory with a main.cpp like this: ``` #include #include "io_world/implementation/hello.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); io_world::Hello myHelloInstance; // Try out properties: subscribe for changes myHelloInstance.connect(&myHelloInstance, &io_world::AbstractHello::lastChanged, []( const io_world::Message& last){ qDebug() << "last property changed ";}); // and ask for change. io_world::Message messageForProperty; messageForProperty.m_content = QString("New message"); myHelloInstance.setLast(messageForProperty); // Check the signals with subscribing for its change myHelloInstance.connect(&myHelloInstance, &io_world::AbstractHello::justSaid, [](const io_world::Message& msg){ qDebug() << "justSaid signal emitted ";}); // and emit one. io_world::Message messageForSignal; messageForSignal.m_content = QString("Message from signal"); emit myHelloInstance.justSaid(messageForSignal); // Play around executing operations, maybe they emit signals? or change the properties? auto method_result = myHelloInstance.say(io_world::Message(), io_world::When::Now); return app.exec(); } ``` add a CMakeLists.txt ``` project(MyExample) cmake_minimum_required(VERSION 3.20) find_package(Qt6 REQUIRED COMPONENTS Gui) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(MyExample main.cpp) find_package(io_world QUIET COMPONENTS io_world_impl) target_link_libraries(MyExample io_world_impl Qt6::Gui) ``` Add the `example` subdirectory to the hello-world/qt\_hello\_world/CMakeLists.txt. Now you can build the application and the libraries with it and run your example. --- # Features This guide explains how to use the generated code, what features are available, and their purpose. info A feature is a part of the template that generates a specific aspect of the code. For example, the `api` feature generates the interface traits and the publisher, while the `stubs` feature generates a default implementation you can build on. ## Get started[​](#get-started "Direct link to Get started") This template generates a [Cargo](https://doc.rust-lang.org/cargo/) workspace for pure *Rust* projects. To successfully compile and use the code you need a working [Rust toolchain](https://www.rust-lang.org/tools/install) (`rustc` and `cargo`, 1.80 or newer). The generated code is asynchronous and built on [tokio](https://tokio.rs/). note Basic Rust knowledge is necessary. ### Code generation[​](#code-generation "Direct link to Code generation") Follow the documentation for [code generation](/docs/guide/quick-start.md) in general and the [CLI](/docs/cli/generate.md) or the [Studio](/docs/studio/intro.md) tools. Or try the [quick start guide](/template-rust/docs/quickstart.md) first, which shows how to prepare an API and generate code from it. tip For questions regarding the template please go to our [discussions page](https://github.com/orgs/apigear-io/discussions). For feature requests or bug reports please use our [issue tracker](https://github.com/apigear-io/template-rust/issues). ### Example API[​](#example-api "Direct link to Example API") The following code snippet contains the *API* definition used throughout this guide to demonstrate the generated code and its usage. Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` ## Features[​](#features-1 "Direct link to Features") ### Core Features[​](#core-features "Direct link to Core Features") The core features generate a working view of your *API* definition. They are self-contained: the `api` and `core` features do **not** depend on any transport or IPC crate, so you can use the generated module crate in a pure in-process application. * [api](/template-rust/docs/features/api.md) - generates the interface traits for your *API*, with awaitable operations and a `Publisher` (built on tokio channels) that exposes property changes and signals. Also generates the `serde`-enabled data structs and enums (`Serialize`/`Deserialize`, `TryFrom` for enums) defined in your module, and the shared `ApiError` and `ApiFuture` types. * [core](/template-rust/docs/features/api.md#data-types-core) - generates per-interface support types under `core_types/`: a property-bundle struct for state synchronization, a shared-reference alias and constructor (`Arc`), and a test helper. * [stubs](/template-rust/docs/features/stubs.md) - adds a ready-to-use default implementation of each interface trait, the workspace `Cargo.toml`, the `examples` crate, and per-interface unit tests. This is a good starting point for your own implementation. note The `stubs` feature requires `api` and `core`. ### Extended Features[​](#extended-features "Direct link to Extended Features") The extended features build on top of `api` and `core` and add more functionality, like monitoring or sharing your data over the network (see [olink](/template-rust/docs/features/olink.md), [mqtt](/template-rust/docs/features/mqtt.md), [nats](/template-rust/docs/features/nats.md)). * [monitor](/template-rust/docs/features/monitor.md) - generates a [`tracing`](https://docs.rs/tracing) decorator that wraps any implementation and logs all operations and state changes to the [CLI](/docs/cli/intro.md) or the [Studio](/docs/studio/intro.md). * [olink](/template-rust/docs/features/olink.md) - provides a client and a service adapter for each interface that can be connected to any of the other technology templates with support for [ObjectLink](/docs/protocols/objectlink/intro.md). Use this feature to connect with the ApiGear simulation tools. Includes in-process loopback round-trip tests. * [mqtt](/template-rust/docs/features/mqtt.md) - provides client and service adapters for each interface over the [MQTT](https://mqtt.org/) protocol (via the [`rumqttc`](https://docs.rs/rumqttc) crate). Check also MQTT in other technology templates that support it. Includes broker-backed integration tests. * [nats](/template-rust/docs/features/nats.md) - provides client and service adapters for each interface over the [NATS](https://nats.io/) protocol (via the [`async-nats`](https://docs.rs/async-nats) crate). Check also NATS in other technology templates that support it. Includes server-backed integration tests. * examples - a shared `examples` crate with runnable programs: a local example exercising every interface in-process, plus `*_server` and `*_client` binaries for each IPC transport (OLink over TCP, MQTT and NATS over a broker/server). Run them with `cargo run`. Each feature can be selected using the solution file or via the command line tool. note *Features are case sensitive, make sure to always **use lower-case.*** tip The *meta* feature `all` enables all specified features of the template. If you want to see the full extent of the generated code, `all` is the easiest solution. Please note, `all` is part of the code generator and not explicitly defined within templates. ## Folder structure[​](#folder-structure "Direct link to Folder structure") This graph shows the folder structure generated with `all` features enabled. Each module becomes its own workspace member crate (here `io_world`), and the shared `examples` crate sits next to it at the workspace root (here `rust_hello_world`). For more details visit the documentation for each feature. ``` 📂hello-world ┣ 📂apigear ┃ ┣ 📜helloworld.solution.yaml ┃ ┗ 📜helloworld.module.yaml ┣ 📂rust_hello_world ┃ ┣ 📜Cargo.toml # workspace manifest ┃ ┣ 📜rustfmt.toml ┃ ┣ 📂examples # runnable example programs (local + IPC client/server) ┃ ┗ 📂io_world # one crate per module ┃ ┃ ┣ 📜Cargo.toml ┃ ┃ ┣ 📂src ┃ ┃ ┃ ┣ 📜lib.rs ┃ ┃ ┃ ┣ 📂api # api feature: traits + Publisher + data structs/enums + ApiError/ApiFuture ┃ ┃ ┃ ┣ 📂core_types # core feature: property bundle + shared reference + test helpers ┃ ┃ ┃ ┣ 📂implementation # stubs feature: default implementation ┃ ┃ ┃ ┣ 📂monitor # monitor feature: tracing decorator ┃ ┃ ┃ ┣ 📂olink # olink feature: client + service adapters ┃ ┃ ┃ ┣ 📂mqtt # mqtt feature: client + service adapters ┃ ┃ ┃ ┗ 📂nats # nats feature: client + service adapters ┃ ┃ ┗ 📂tests # unit tests + IPC round-trip tests ``` --- # API Feature The feature `api` is the bare minimum for code generation. It generates the trait-based API for your interfaces, the data types (structs and enums) defined in your module, a publisher for property changes and signals, and the shared error and future types. Together with the [core](#data-types-core) feature it produces a self-contained crate that compiles without any transport dependency. The `api` feature generates: * a trait for each defined *interface*, with awaitable operations and property accessors * an ergonomic `_async` companion for every operation * the `serde`-enabled [data structs and enums](#data-structs-and-enums) defined in your module * a [`Publisher`](#publisher) per interface, exposing property changes and signals over tokio channels * the shared [`ApiError` and `ApiFuture`](#apierror-and-apifuture) types note Check out the [stubs](/template-rust/docs/features/stubs.md) feature, which provides a ready-to-use implementation of every trait generated here. ### Files overview per module[​](#files-overview-per-module "Direct link to Files overview per module") Using the example API definition Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following file structure is generated inside the module crate. The purpose and content of each file is explained below. ``` 📂io_world ┣ 📂src ┃ ┣ 📂api ┃ ┃ ┣ 📜mod.rs # re-exports for the api module ┃ ┃ ┣ 📜api_common.rs # ApiError + ApiFuture ┃ ┃ ┣ 📜data_structs.rs # module-wide enums/structs (Message, WhenEnum) ┃ ┃ ┗ 📜hello.rs # HelloTrait, HelloTraitAsync, HelloPublisher ┃ ┗ 📜lib.rs ... ``` For each interface in the module a separate file is generated, like `📜hello.rs`. It contains the interface trait, its async companion trait, and the publisher. ## Interface trait[​](#interface-trait "Direct link to Interface trait") Every interface becomes a trait that is `Send + Sync`, so an implementation can be shared across threads and `await` points. For the `Hello` interface you get a `HelloTrait`: ``` use crate::api::data_structs::*; use crate::api::{ApiError, ApiFuture}; pub trait HelloTrait: Send + Sync { /// Operations return an awaitable, object-safe `ApiFuture`. fn say( &self, msg: &Message, when: WhenEnum, ) -> ApiFuture<'_, Result>; /// Gets the value of the last property. fn last(&self) -> Message; /// Sets the value of the last property. fn set_last( &self, last: &Message, ); fn publisher(&self) -> &HelloPublisher; } ``` The trait has: * an **operation** for each method in the interface. Operations are object-safe and awaitable: they return an `ApiFuture` (see [below](#apierror-and-apifuture)). Returning a boxed future — instead of using `async fn` directly — keeps the trait object-safe, so it can be used behind `Arc`. * a **getter and setter** for each property (here `last()` / `set_last()`). * an accessor for the [`Publisher`](#publisher). note The trait itself does not contain signals — signal emission and property-change notification are handled by the [`Publisher`](#publisher). ### Async operations[​](#async-operations "Direct link to Async operations") Calling an operation through the trait returns a future you `await`: ``` let result: Result = object.say(&message, WhenEnum::Now).await; ``` For convenience, every interface also generates an **extension trait** `HelloTraitAsync` with an `_async` companion for each operation. It is provided through a blanket implementation for every implementor — including `dyn HelloTrait` — so it works on a trait object behind an `Arc` without any extra setup: ``` pub trait HelloTraitAsync: HelloTrait { fn say_async( &self, msg: &Message, when: WhenEnum, ) -> impl std::future::Future> + Send { async move { self.say(msg, when).await } } } impl HelloTraitAsync for T {} ``` Bring the `_async` trait into scope to call the ergonomic variant: ``` use io_world::api::hello::HelloTrait; use io_world::api::hello::HelloTraitAsync; let said = object.say_async(&message, WhenEnum::Now).await?; ``` Both forms do the same work; `say()` returns the boxed `ApiFuture` (useful in object-safe contexts), while `say_async()` reads more naturally at the call site. ## Publisher[​](#publisher "Direct link to Publisher") The interface trait does not expose signals or change callbacks directly. Instead, each interface gets a `Publisher` struct that broadcasts property changes and signal emissions over [tokio](https://tokio.rs/) channels. Subscribers receive a channel receiver and react to changes asynchronously. ``` use tokio::sync::{watch, broadcast}; pub struct HelloPublisher { /// Property changes are delivered over a `watch` channel (latest value). pub last_changed: watch::Sender, /// Signals are delivered over a `broadcast` channel (every emission). pub just_said: broadcast::Sender<(Message,)>, } ``` Each property gets a `watch::Sender` named `_changed` (here `last_changed`), and each signal gets a `broadcast::Sender` named after the signal (here `just_said`). * **Property changes** use a tokio [`watch`](https://docs.rs/tokio/latest/tokio/sync/watch/) channel. A `watch` channel always holds the latest value, so a new subscriber immediately sees the current state. The implementation sends on the channel whenever the property changes. * **Signals** use a tokio [`broadcast`](https://docs.rs/tokio/latest/tokio/sync/broadcast/) channel, so every subscriber receives every emission. Signal payloads are delivered as a tuple of the signal parameters (here `(Message,)`). ### Subscribe to changes and signals[​](#subscribe-to-changes-and-signals "Direct link to Subscribe to changes and signals") Subscribe by calling `subscribe()` on the relevant channel sender exposed by `publisher()`: ``` // React to property changes: let mut last_rx = object.publisher().last_changed.subscribe(); tokio::spawn(async move { while last_rx.changed().await.is_ok() { let last = last_rx.borrow().clone(); println!("last property changed: {last:?}"); } }); // React to a signal: let mut said_rx = object.publisher().just_said.subscribe(); tokio::spawn(async move { while let Ok((msg,)) = said_rx.recv().await { println!("justSaid emitted: {msg:?}"); } }); ``` note It is the implementation's responsibility to send on the publisher's channels when a property changes or a signal is emitted. The [stubs](/template-rust/docs/features/stubs.md) feature already does this for you — see the generated default implementation. ## ApiError and ApiFuture[​](#apierror-and-apifuture "Direct link to ApiError and ApiFuture") The shared `ApiError` and `ApiFuture` types are defined in `📜api_common.rs`, inside the `api` module. They have no transport or IPC dependency, so the `api` and `core` features stay self-contained. `ApiFuture` is a boxed, pinned, `Send` future — the return type of every trait operation: ``` use std::future::Future; use std::pin::Pin; /// A boxed, pinned future returned by async trait operations. /// Named `ApiFuture` (not `ApiResult`) because it wraps a `Future`, not a `Result`. pub type ApiFuture<'a, T> = Pin + Send + 'a>>; ``` `ApiError` is a [`thiserror`](https://docs.rs/thiserror)-derived enum covering the failure modes shared by local and IPC implementations: ``` #[derive(Debug, thiserror::Error)] pub enum ApiError { #[error("operation failed: {0}")] OperationFailed(String), #[error("not connected")] NotConnected, #[error("serialization error: {0}")] Serialization(#[from] serde_json::Error), #[error("transport error: {0}")] Transport(String), } ``` Local implementations typically return `Ok(...)`, while the IPC client adapters use `NotConnected` and `Transport` when the remote service is unavailable. ## Data structs and enums[​](#data-structs-and-enums "Direct link to Data structs and enums") The `api` feature also generates the data types defined in your module — the structs and enums referenced by your interfaces — into `📜src/api/data_structs.rs`. They carry `serde` derives so they can be serialized for the IPC transports. Bring them into scope with `use crate::api::data_structs::*;` (as the generated trait, implementation and adapter files do). ### Structs[​](#structs "Direct link to Structs") Each struct in your API becomes a Rust struct with `serde` derives: ``` use serde::{Deserialize, Serialize}; #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] pub struct Message { pub content: String, } ``` The derives give you, for free: * `Default` for zero-value construction * `Clone` and `PartialEq` for copying and comparison * `Serialize`/`Deserialize` for the IPC transports ### Enums[​](#enums "Direct link to Enums") Each enum becomes a `#[repr(u8)]` Rust enum with `serde` derives and a `TryFrom` conversion for decoding wire values: ``` use serde::{Deserialize, Serialize}; use std::convert::TryFrom; #[repr(u8)] #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq)] pub enum WhenEnum { #[default] Now = 0, Soon = 1, Never = 2, } impl TryFrom for WhenEnum { type Error = (); fn try_from(value: u8) -> Result { match value { 0 => Ok(WhenEnum::Now), 1 => Ok(WhenEnum::Soon), 2 => Ok(WhenEnum::Never), _ => Err(()), } } } ``` The first member is marked `#[default]`, so `WhenEnum::default()` and `Default::default()` resolve to it. ## Data types (core)[​](#data-types-core "Direct link to Data types (core)") The [`core`](/template-rust/docs/features.md#core-features) feature generates the per-interface support types used for state synchronization and for passing implementations around, under `📂src/core_types/`. It does **not** generate your structs and enums — those come from the [`api`](#data-structs-and-enums) feature above. `core` is enabled automatically when you use [stubs](/template-rust/docs/features/stubs.md) or any extended feature. For each interface it generates: * a **property bundle** struct that gathers all of the interface's properties for state sync * a **shared-reference** type alias and constructor for handing the implementation around as an `Arc` * a **test helper** that builds a populated instance of each data struct ### Property bundle[​](#property-bundle "Direct link to Property bundle") The property bundle (`📜src/core_types/hello_data.rs`) collects every property of the interface into one `serde`-enabled struct. The IPC adapters use it to send and receive the full initial state in a single message: ``` use crate::api::data_structs::*; use serde::{Deserialize, Serialize}; /// Bundles all properties of Hello for state synchronization. #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] pub struct HelloData { pub last: Message, } ``` ### Shared reference[​](#shared-reference "Direct link to Shared reference") A type alias and constructor (`📜src/core_types/hello_shared.rs`) let you hand a trait object around as an `Arc`: ``` use std::sync::Arc; use crate::api::hello::HelloTrait; use crate::implementation::hello::Hello; /// Shared reference to a Hello implementation. pub type SharedHello = Arc; /// Creates a new shared Hello with the default implementation. pub fn new_shared_hello() -> SharedHello { Arc::new(Hello::default()) } ``` This `Arc` is exactly what the [monitor](/template-rust/docs/features/monitor.md), [olink](/template-rust/docs/features/olink.md), [mqtt](/template-rust/docs/features/mqtt.md) and [nats](/template-rust/docs/features/nats.md) features wrap, so the same implementation can be used locally and exposed over the network. note `new_shared_hello()` builds the default [stub](/template-rust/docs/features/stubs.md) implementation, so the shared-reference helper is only generated when `stubs` is enabled. The `core` feature also generates a `📜test_helpers.rs` with a `fill_test_*` function per data struct, used by the generated tests. --- # Monitor Use the monitor feature to observe interface calls and state changes at runtime. It generates a [`tracing`](https://docs.rs/tracing) decorator that wraps any implementation of your interface and emits a tracing event for every operation and property change. Because the decorator implements the same interface trait, you can drop it in wherever your implementation is used. The monitoring server is embedded into [ApiGear Studio](/docs/studio/intro.md) and the [CLI](/docs/cli/intro.md). For more details see the [monitoring documentation](/docs/monitor/intro.md). note This feature requires `api` and `core`. ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With our example API definition Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following files are generated. The purpose and content of each file is explained below. ``` 📂io_world ┣ 📂src ┃ ┣ 📂monitor ┃ ┃ ┣ 📜mod.rs ┃ ┃ ┗ 📜hello_traced.rs # tracing decorator for Hello ┃ ┗ 📜lib.rs ... ``` ## The tracing decorator[​](#the-tracing-decorator "Direct link to The tracing decorator") The file `📜hello_traced.rs` contains a `HelloTraced` decorator. It is generic over any `T: HelloTrait`, wraps an inner implementation, and implements `HelloTrait` itself by logging a [`tracing`](https://docs.rs/tracing) event and then forwarding the call to the inner object. ``` use crate::api::data_structs::*; use crate::api::{ApiError, ApiFuture}; use crate::api::hello::HelloPublisher; use crate::api::hello::HelloTrait; use tracing; /// Trace decorator for Hello. /// Wraps any implementation and instruments all operations with tracing spans. pub struct HelloTraced { inner: T, } impl HelloTraced { pub fn new(inner: T) -> Self { Self { inner } } } impl HelloTrait for HelloTraced { fn say( &self, msg: &Message, when: WhenEnum, ) -> ApiFuture<'_, Result> { tracing::info!("Hello::say called"); self.inner.say(msg, when) } fn last(&self) -> Message { self.inner.last() // getters are forwarded without tracing } fn set_last( &self, last: &Message, ) { tracing::info!("Hello::set_last called"); self.inner.set_last(last); } fn publisher(&self) -> &HelloPublisher { self.inner.publisher() } } ``` Operations and property setters are logged; getters and the `publisher()` accessor are forwarded unchanged. Since `HelloTraced` is itself a `HelloTrait`, you can use it anywhere the plain implementation fits — including in front of an IPC client adapter. note The wrapped `inner` value just has to satisfy the `HelloTrait` bound, so it can be the default [stub](/template-rust/docs/features/stubs.md) implementation, an [OLink client](/template-rust/docs/features/olink.md), or any other implementation. ## Use the decorator[​](#use-the-decorator "Direct link to Use the decorator") Wrap your implementation with `HelloTraced::new(...)` and use the result as a `Hello` object. Every traced call produces a `tracing` event that a configured subscriber can forward to the ApiGear monitoring server. ``` use io_world::api::hello::HelloTrait; use io_world::api::hello::HelloTraitAsync; use io_world::implementation::hello::Hello; use io_world::monitor::hello_traced::HelloTraced; #[tokio::main(flavor = "multi_thread")] async fn main() { // Set up a tracing subscriber (here: log to stdout). tracing_subscriber::fmt::init(); // Wrap the default implementation with the tracing decorator. let hello = HelloTraced::new(Hello::default()); // Use it exactly like a Hello implementation — every call is traced. let _ = hello.say_async(&Default::default(), Default::default()).await; hello.set_last(&Default::default()); } ``` tip The `tracing` crate decouples *emitting* events from *collecting* them. Install any [`tracing-subscriber`](https://docs.rs/tracing-subscriber) layer to control where the events go — the console for local development, or an exporter that forwards them to the [ApiGear Studio](/docs/studio/intro.md) or [CLI](/docs/cli/intro.md) monitor. --- # MQTT This feature provides a *client* and a *service* adapter for your interfaces over the [MQTT](https://mqtt.org/) protocol, built on the [`rumqttc`](https://docs.rs/rumqttc) crate. It lets you connect applications built with the same or different technologies — check all of our [templates](/docs/sdk/intro.md) and the MQTT feature in other templates that support it. * Use an *MQTT client* in place of your local implementation to receive data from a remote service. * Use an *MQTT service adapter* to expose your implementation as a remote service. note This feature requires `api` and `core`. tip The MQTT broker is not part of the template. To run a client and a service you need a broker (for example [Mosquitto](https://mosquitto.org/)) reachable by both. ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With our example API definition Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following files are generated. The purpose and content of each file is explained below. ``` 📂io_world ┣ 📂src ┃ ┣ 📂mqtt ┃ ┃ ┣ 📜mod.rs ┃ ┃ ┣ 📜hello_client.rs # MQTT client adapter for Hello ┃ ┃ ┗ 📜hello_service.rs # MQTT service adapter for Hello ┃ ┗ 📜lib.rs ┣ 📂tests ┃ ┣ 📜mqtt_common.rs # broker test helper ┃ ┗ 📜mqtt_hello_test.rs # round-trip tests for Hello ... ``` The adapters speak the agreed ApiGear MQTT (MQTT 5) wire scheme, so a Rust client or service interoperates with the services and clients generated by the other ApiGear templates (C++, Qt, Python, …) over the same broker. For module `io.world` / interface `Hello` the topics are: | Message | Topic | Direction | | ----------------------- | ---------------------------------------------------------------------------------- | ---------------- | | operation request | `io.world/Hello/rpc/` | client → service | | operation reply | the request's MQTT 5 `ResponseTopic` (`io.world/Hello/rpc///result`) | service → client | | property change request | `io.world/Hello/set/` | client → service | | property notification | `io.world/Hello/prop/` (retained) | service → client | | signal | `io.world/Hello/sig/` | service → client | Operation replies are correlated using the request's MQTT 5 `CorrelationData`, which the service echoes back. Because property notifications are retained, a client that connects later still receives the current value (the MQTT scheme has no separate state topic). See **[ApiGear over MQTT](/docs/protocols/mqtt/mapping.md)** for the topic structure and payload format. ## MQTT client adapter[​](#mqtt-client-adapter "Direct link to MQTT client adapter") The file `📜hello_client.rs` contains `HelloMqttClient`, the MQTT client version of the `Hello` interface. It implements `HelloTrait`, so you use it like a local implementation. It takes a shared [`rumqttc`](https://docs.rs/rumqttc) v5 `AsyncClient` and a unique `client_id` (used to route RPC replies), subscribes to the interface's topics, and decodes incoming messages. ``` let client = Arc::new(HelloMqttClient::new(Arc::new(mqtt_async_client), "hello-client")); client.subscribe_topics().await.expect("subscribe to topics"); ``` #### Properties[​](#properties "Direct link to Properties") A getter (here `last()`) returns the locally cached value last received from the service. A setter (here `set_last()`) publishes a change request; the local value updates when the service confirms the change. Subscribe to changes through the [`Publisher`](/template-rust/docs/features/api.md#publisher) returned by `publisher()`. note Property notifications are retained on the broker, so a client connecting after the service started still receives the current property values. #### Operations[​](#operations "Direct link to Operations") Operations are published as request messages and awaited: ``` let result = client.say(&message, WhenEnum::Now).await; ``` #### Signals[​](#signals "Direct link to Signals") Do not emit signals from a client. Subscribe to signals through the [`Publisher`](/template-rust/docs/features/api.md#publisher); incoming signal messages are delivered on the matching `broadcast` channel. ## MQTT service adapter[​](#mqtt-service-adapter "Direct link to MQTT service adapter") The file `📜hello_service.rs` contains `HelloMqttService`, which wraps a local `Hello` implementation and exposes it over MQTT. It applies incoming operation and property-change requests to your local object and publishes property changes and signals back to clients. * **Properties** — a change on your local object (or a client request) is published to all clients. * **Operations** — a request is run on your local object; the result is returned only to the requesting client. * **Signals** — a signal emitted by your local object is forwarded to all clients. ## Use the adapters[​](#use-the-adapters "Direct link to Use the adapters") The generated `examples` crate ships ready-to-run `mqtt_server` and `mqtt_client` binaries. The client creates a `rumqttc` v5 `AsyncClient`, hands it to the adapter, and pumps the MQTT event loop so the adapter receives RPC replies, property changes and signals. The incoming MQTT 5 `CorrelationData` is forwarded to the adapter so it can match RPC replies: ``` use rumqttc::v5::mqttbytes::v5::Packet; use rumqttc::v5::{AsyncClient, Event, MqttOptions}; use std::sync::Arc; use std::time::Duration; let client_id = "hello-client"; let mut opts = MqttOptions::new(client_id, "127.0.0.1", 1883); opts.set_keep_alive(Duration::from_secs(5)); let (mqtt, mut eventloop) = AsyncClient::new(opts, 64); let client = Arc::new(HelloMqttClient::new(Arc::new(mqtt), client_id)); client.subscribe_topics().await.expect("subscribe to topics"); // Drive the event loop so incoming messages reach the adapter. let pump = client.clone(); tokio::spawn(async move { loop { if let Ok(Event::Incoming(Packet::Publish(p))) = eventloop.poll().await { let topic = String::from_utf8_lossy(&p.topic); let correlation_data = p.properties.as_ref().and_then(|pr| pr.correlation_data.as_deref()); pump.handle_message(&topic, &p.payload, correlation_data); } } }); // Use the client like a local Hello implementation; the call awaits the RPC reply: let _ = client.say(&Default::default(), WhenEnum::Now).await; ``` Start a broker, then run the two binaries in separate terminals (override the broker port with the `MQTT_PORT` environment variable, default `1883`): ``` mosquitto -p 1883 & cargo run -p rust_hello_world_examples --bin mqtt_server cargo run -p rust_hello_world_examples --bin mqtt_client ``` ## Tests[​](#tests "Direct link to Tests") The MQTT feature generates round-trip tests in `📜tests/mqtt_hello_test.rs`, backed by the helper in `📜mqtt_common.rs`. They exercise a real client ↔ service round-trip over a live broker, so they are marked `#[ignore]` and skipped by default. Run them against a broker the way CI does: ``` mosquitto -p 1883 & cargo test --manifest-path goldenmaster/Cargo.toml -- --ignored ``` --- # NATS This feature provides a *client* and a *service* adapter for your interfaces over the [NATS](https://nats.io/) protocol, built on the [`async-nats`](https://docs.rs/async-nats) crate. It lets you connect applications built with the same or different technologies — check all of our [templates](/docs/sdk/intro.md) and the NATS feature in other templates that support it. * Use a *NATS client* in place of your local implementation to receive data from a remote service. * Use a *NATS service adapter* to expose your implementation as a remote service. note This feature requires `api` and `core`. tip The NATS server is not part of the template. To run a client and a service (both connect as NATS clients) you need a [nats-server](https://nats.io/download/) reachable by both. ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With our example API definition Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following files are generated. The purpose and content of each file is explained below. ``` 📂io_world ┣ 📂src ┃ ┣ 📂nats ┃ ┃ ┣ 📜mod.rs ┃ ┃ ┣ 📜hello_client.rs # NATS client adapter for Hello ┃ ┃ ┗ 📜hello_service.rs # NATS service adapter for Hello ┃ ┗ 📜lib.rs ┣ 📂tests ┃ ┣ 📜nats_common.rs # server test helper ┃ ┗ 📜nats_hello_test.rs # round-trip tests for Hello ... ``` The adapters speak the agreed ApiGear NATS wire scheme, so a Rust client or service interoperates with the services and clients generated by the other ApiGear templates (C++, Qt, Python, …) against the same nats-server. For module `io.world` / interface `Hello` the subjects are: | Message | Subject | Direction | | ----------------------- | ----------------------------------------------------- | ------------------------- | | operation request/reply | `io.world.Hello.rpc.` (native NATS request/reply) | client → service → client | | property change request | `io.world.Hello.set.` | client → service | | property notification | `io.world.Hello.prop.` | service → client | | signal | `io.world.Hello.sig.` | service → client | | availability | `io.world.Hello.service.available` | service → client | | init request | `io.world.Hello.init` (payload = client id) | client → service | | init reply | `io.world.Hello.init.resp.` (current state) | service → client | See **[ApiGear over NATS](/docs/protocols/nats/mapping.md)** for the subject structure and payload format. ## NATS client adapter[​](#nats-client-adapter "Direct link to NATS client adapter") The file `📜hello_client.rs` contains `HelloNatsClient`, the NATS client version of the `Hello` interface. It implements `HelloTrait`, so you use it like a local implementation. It takes a connected [`async-nats`](https://docs.rs/async-nats) client and subscribes to the interface's subjects. ``` let nats = async_nats::connect("127.0.0.1:4222").await?; let client = Arc::new(HelloNatsClient::new(nats)); let _subscription = client.subscribe(); ``` #### Properties[​](#properties "Direct link to Properties") A getter (here `last()`) returns the locally cached value last received from the service. A setter (here `set_last()`) sends a change request; the local value updates when the service confirms the change. Subscribe to changes through the [`Publisher`](/template-rust/docs/features/api.md#publisher) returned by `publisher()`. #### Operations[​](#operations "Direct link to Operations") Operations use NATS request/reply — the call sends a request and awaits the reply: ``` let result = client.say(&message, WhenEnum::Now).await; ``` #### Signals[​](#signals "Direct link to Signals") Do not emit signals from a client. Subscribe to signals through the [`Publisher`](/template-rust/docs/features/api.md#publisher); incoming signal messages are delivered on the matching `broadcast` channel. #### Connectivity[​](#connectivity "Direct link to Connectivity") The client's `subscribe()` spawns a background task that subscribes to the property (`io.world.Hello.prop.*`), signal (`io.world.Hello.sig.*`), availability (`io.world.Hello.service.available`) and init-reply (`io.world.Hello.init.resp.`) subjects, and keeps the cache in sync as messages arrive. On start-up — and whenever the service (re)announces availability — the client sends an `init` request and the service replies with the current state on `init.resp.`. Because NATS does not retain messages, this handshake is how a client that connects after the service started obtains the current property values; the service announces `service.available` once it is ready so clients can re-sync. ## NATS service adapter[​](#nats-service-adapter "Direct link to NATS service adapter") The file `📜hello_service.rs` contains `HelloNatsService`, which wraps a local `Hello` implementation and exposes it over NATS. It applies incoming operation and property-change requests to your local object and publishes property changes and signals back to clients. * **Properties** — a change on your local object (or a client request) is published to all clients. * **Operations** — a request is run on your local object; the result is returned only to the requesting client. * **Signals** — a signal emitted by your local object is forwarded to all clients. ## Use the adapters[​](#use-the-adapters "Direct link to Use the adapters") The generated `examples` crate ships ready-to-run `nats_server` and `nats_client` binaries. The client connects to the server, hands the connection to the adapter, and subscribes: ``` use std::sync::Arc; use std::time::Duration; let nats = async_nats::connect("127.0.0.1:4222").await.expect("connect to nats-server"); let client = Arc::new(HelloNatsClient::new(nats)); let _subscription = client.subscribe(); // Give the subscriptions and state exchange a moment. tokio::time::sleep(Duration::from_millis(500)).await; // Use the client like a local Hello implementation: let result = client.say(&Default::default(), WhenEnum::Now).await; println!("say() -> {result:?}"); ``` Start a server, then run the two binaries in separate terminals (override the server URL with the `NATS_URL` environment variable, default `127.0.0.1:4222`): ``` nats-server -p 4222 & cargo run -p rust_hello_world_examples --bin nats_server cargo run -p rust_hello_world_examples --bin nats_client ``` ## Tests[​](#tests "Direct link to Tests") The NATS feature generates round-trip tests in `📜tests/nats_hello_test.rs`, backed by the helper in `📜nats_common.rs`. They exercise a real client ↔ service round-trip over a live server, so they are marked `#[ignore]` and skipped by default. Run them against a server the way CI does: ``` nats-server -p 4222 & cargo test --manifest-path goldenmaster/Cargo.toml -- --ignored ``` --- # OLink This feature provides a *client* and a *service* adapter for your interfaces over the [ObjectLink](/docs/protocols/objectlink/intro.md) protocol. It lets you connect applications built with the same or different technologies — check all of our [templates](/docs/sdk/intro.md). Use an *OLink client* in place of your local implementation to talk to a remote service or to the [ApiGear simulation](#simulation). Use an *OLink service adapter* to expose your implementation as a remote service. note This feature requires `api` and `core`. ### ApiGear ObjectLink protocol[​](#apigear-objectlink-protocol "Direct link to ApiGear ObjectLink protocol") The [ObjectLink](/docs/protocols/objectlink/intro.md) protocol is a lightweight message protocol for objects described by an interface. It connects a client object with a server object and supports remote operations: requesting a property change (client) or notifying a property change (server), emitting a signal (server), and invoking a remote method with a response delivered to the caller (server). The OLink adapters build on the [objectlink-core-rs](https://github.com/apigear-io/objectlink-core-rs) library (the `objectlink-core` crate), which is shared across Rust-based ObjectLink code. It provides the protocol abstraction — encoding and decoding messages and routing them to the right object through a registry — independent of the network stack. Messages are exchanged as newline-delimited JSON, so any byte stream (a TCP socket, an in-process loopback) can carry them. ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With our example API Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following files are generated. The purpose and content of each file is explained below. ``` 📂io_world ┣ 📂src ┃ ┣ 📂olink ┃ ┃ ┣ 📜mod.rs ┃ ┃ ┣ 📜hello_client.rs # OLink client adapter for Hello ┃ ┃ ┗ 📜hello_service.rs # OLink service adapter for Hello ┃ ┗ 📜lib.rs ┣ 📂tests ┃ ┣ 📜olink_common.rs # in-process loopback helper ┃ ┗ 📜olink_hello_test.rs # round-trip tests for Hello ... ``` ## OLink client adapter[​](#olink-client-adapter "Direct link to OLink client adapter") The file `📜hello_client.rs` contains `HelloOlinkClient`, the OLink client version of the `Hello` interface. It implements two traits: `HelloTrait` (so you can use it exactly like a local implementation) and `ObjectSink` (so the OLink core can deliver incoming messages to it). It caches property values locally and forwards operations and property writes to the remote service. ``` pub struct HelloOlinkClient { data: RwLock, // locally cached property values node: RwLock>>, publisher: HelloPublisher, } ``` #### Properties[​](#properties "Direct link to Properties") A property getter (here `last()`) returns the locally cached value last received from the service. A property setter (here `set_last()`) sends a change request to the service; the local value is **not** changed immediately — it updates when the service confirms the change. Subscribe to changes through the [`Publisher`](/template-rust/docs/features/api.md#publisher) returned by `publisher()`. When the client receives a property-change message, it updates its cache and sends on the matching `watch` channel. note On a successful link, the client receives the service's current state, so its cached properties start in sync with the service. #### Operations[​](#operations "Direct link to Operations") Operations are forwarded as remote invocations. Calling an operation sends an invoke request to the service and awaits the reply: ``` let result = client.say(&message, WhenEnum::Now).await; ``` If no client node has been set or the link is down, the operation resolves to `Err(ApiError::NotConnected)`. #### Signals[​](#signals "Direct link to Signals") Do not emit signals from a client. Subscribe to any signal through the [`Publisher`](/template-rust/docs/features/api.md#publisher); when the client receives a signal message it sends on the matching `broadcast` channel. ## OLink service adapter[​](#olink-service-adapter "Direct link to OLink service adapter") The file `📜hello_service.rs` contains `HelloOlinkService`, which wraps a local `Hello` implementation and exposes it to remote clients by implementing the `ObjectSource` trait from [objectlink-core-rs](https://github.com/apigear-io/objectlink-core-rs). It receives remote invocations and property-change requests, applies them to your local object, and pushes property changes and signals back to connected clients. Construct it with the shared implementation you want to expose: ``` let object: Arc = Arc::new(Hello::default()); let service = HelloOlinkService::new(object.clone()); ``` * **Properties** — a property change on your local object (or a change requested by a client) is published to all connected clients. * **Operations** — a remote invocation is run on your local object; the result is returned only to the calling client. * **Signals** — a signal emitted by your local object is forwarded to all connected clients. ## Use the adapters[​](#use-the-adapters "Direct link to Use the adapters") The adapters need a transport to carry the ObjectLink messages. The generated `examples` crate ships ready-to-run `olink_server` and `olink_client` binaries that wire the adapters to a TCP socket. The service side registers the adapter and pumps socket lines through a `RemoteNode`: ``` use objectlink_core::remote_node::RemoteNode; use objectlink_core::remote_registry::RemoteRegistry; use objectlink_core::traits::ObjectSource; use std::sync::Arc; let object = Arc::new(Hello::default()); let service: Arc = Arc::new(HelloOlinkService::new(object.clone() as Arc)); let registry = Arc::new(RemoteRegistry::new()); registry.add_source(Arc::downgrade(&service)); // Accept TCP connections and feed each incoming line to `node.handle_message(&line)`. ``` The client side connects a `HelloOlinkClient` through a `ClientNode` and links it to the remote object: ``` use objectlink_core::client_node::ClientNode; use objectlink_core::client_registry::ClientRegistry; use objectlink_core::traits::ObjectSink; use std::sync::Arc; let registry = Arc::new(ClientRegistry::new()); let node = Arc::new(ClientNode::new(registry)); let client = Arc::new(HelloOlinkClient::default()); client.set_node(node.clone()); let sink: Arc = client.clone(); node.link_remote(&sink); // Once linked, use the client like a local Hello implementation: let _ = client.say(&Default::default(), WhenEnum::Now).await; println!("last = {:?}", client.last()); ``` Run the two binaries in separate terminals (override the address with the `OLINK_ADDR` environment variable, default `127.0.0.1:8182`): ``` cargo run -p rust_hello_world_examples --bin olink_server cargo run -p rust_hello_world_examples --bin olink_client ``` ## Tests[​](#tests "Direct link to Tests") The OLink feature generates round-trip tests in `📜tests/olink_hello_test.rs`, backed by an in-process loopback helper in `📜olink_common.rs`. The loopback wires a service adapter and a client adapter together without a socket, so the tests verify the full client ↔ service round-trip — operations, property writes, remote property notifications, and signals — entirely in-memory. They run as part of `cargo test` and require no broker: ``` cargo test --manifest-path goldenmaster/Cargo.toml ``` ## Simulation[​](#simulation "Direct link to Simulation") The simulation lets you test, demonstrate or develop applications without the real service. The simulation server is integrated into [ApiGear Studio](/docs/studio/intro.md) and the [CLI](/docs/cli/simulate.md). Because the simulation speaks ObjectLink, point your `HelloOlinkClient` at the simulation server's address instead of a real service and it behaves like a remote implementation. You drive the simulation with [simulation scenarios](/docs/scripting/backends/scenario.md) — YAML files that define sequences of actions that change property values or emit signals. See more on [simulation](/docs/scripting/backends/intro.md). Run a scenario from the CLI and connect your client to the same port: ``` apigear simulate run path/to/helloworldtest.scenario.yaml --addr :8182 ``` --- # Stubs The feature `stubs` turns the bare [api](/template-rust/docs/features/api.md) traits into a runnable workspace. It adds: * a ready-to-use default implementation of every interface trait — a good starting point for your own logic * the workspace `Cargo.toml` and `rustfmt.toml` * the `examples` crate with runnable programs * per-interface unit tests note The `stubs` feature requires `api` and `core`. Enabling it pulls them in automatically. ### File overview[​](#file-overview "Direct link to File overview") With our example API definition Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` the following files are generated. The purpose and content of each file is explained below. ``` 📂rust_hello_world ┣ 📜Cargo.toml # workspace manifest (system scope) ┣ 📜rustfmt.toml ┣ 📂examples # runnable example programs (system scope) ┃ ┣ 📜Cargo.toml ┃ ┗ 📂src ┃ ┃ ┣ 📜main.rs # local in-process example ┃ ┃ ┗ 📂bin # IPC client/server binaries ┃ ┗ ... ┗ 📂io_world ┃ ┣ 📜Cargo.toml # module crate manifest ┃ ┣ 📂src ┃ ┃ ┣ 📜lib.rs ┃ ┃ ┗ 📂implementation ┃ ┃ ┃ ┣ 📜mod.rs ┃ ┃ ┃ ┗ 📜hello.rs # default Hello implementation ┃ ┗ 📂tests ┃ ┃ ┗ 📜implementation_hello_test.rs # unit tests for Hello ``` ## Implementation[​](#implementation "Direct link to Implementation") The file `📜implementation/hello.rs` contains the default implementation of `HelloTrait`. It is a regular struct that: * stores each property behind a [`parking_lot::RwLock`](https://docs.rs/parking_lot) for interior mutability, so the methods take `&self` (matching the object-safe trait) yet can still mutate state * owns a [`HelloPublisher`](/template-rust/docs/features/api.md#publisher) and returns it through `publisher()` * implements the property getters and setters, sending on the publisher's channel on every actual change * provides a default body for each operation for you to fill with your business logic ``` use crate::api::hello::HelloTrait; use crate::api::data_structs::*; use crate::api::{ApiError, ApiFuture}; use crate::api::hello::HelloPublisher; use parking_lot::RwLock; pub struct Hello { last: RwLock, publisher: HelloPublisher, } impl Default for Hello { fn default() -> Self { Self { last: RwLock::new(Default::default()), publisher: Default::default() } } } impl HelloTrait for Hello { fn say( &self, _msg: &Message, _when: WhenEnum, ) -> ApiFuture<'_, Result> { // Fill in your business logic here. Box::pin(async move { Ok(Default::default()) }) } fn last(&self) -> Message { self.last.read().clone() } fn set_last( &self, last: &Message, ) { let new_val = last.clone(); let mut value = self.last.write(); if *value == new_val { return; // skip notification if the value did not change } *value = new_val.clone(); // Notify subscribers of the change. let _ = self.publisher.last_changed.send(new_val); } fn publisher(&self) -> &HelloPublisher { &self.publisher } } ``` tip When adding your own logic, remember to send on the publisher's channels each time you want a property change to be shared or a signal to be emitted — just like the generated setters do. note The setter skips the notification when the new value equals the current one, so subscribers are only woken on a real change. ### Shared constructor[​](#shared-constructor "Direct link to Shared constructor") Alongside each implementation, the [core](/template-rust/docs/features/api.md#data-types-core) feature generates a shared type alias and constructor in `📜src/core_types/hello_shared.rs`, so you can hand a trait object around as an `Arc`: ``` use std::sync::Arc; use crate::api::hello::HelloTrait; use crate::implementation::hello::Hello; /// Shared reference to a Hello implementation. pub type SharedHello = Arc; /// Creates a new shared Hello with the default implementation. pub fn new_shared_hello() -> SharedHello { Arc::new(Hello::default()) } ``` This `Arc` is exactly what the [monitor](/template-rust/docs/features/monitor.md), [olink](/template-rust/docs/features/olink.md), [mqtt](/template-rust/docs/features/mqtt.md) and [nats](/template-rust/docs/features/nats.md) features wrap, so the same implementation can be used locally and exposed over the network. ## The examples crate[​](#the-examples-crate "Direct link to The examples crate") The `📂examples` crate is generated once per workspace (system scope). Its `src/main.rs` instantiates every interface's default implementation in-process and exercises it — calling the first operation through the `_async` companion, setting each property, and reporting how many signals are available through the publisher. Run it with: ``` cargo run -p rust_hello_world_examples ``` The `examples/src/bin/` folder additionally contains `*_server` and `*_client` binaries for each IPC transport. They are documented with the [olink](/template-rust/docs/features/olink.md), [mqtt](/template-rust/docs/features/mqtt.md) and [nats](/template-rust/docs/features/nats.md) features. ## Build on the stub[​](#build-on-the-stub "Direct link to Build on the stub") The default implementation is the recommended starting point. A typical workflow: 1. Generate with the `stubs` feature (and any IPC features you need). 2. Fill in the operation bodies in `📂implementation/*.rs` with your logic. 3. Send on the publisher's signal channels wherever your logic emits a signal. 4. Use your implementation locally, or wrap it with a [monitor](/template-rust/docs/features/monitor.md) decorator or an IPC service adapter to share it over the network. note Set `force: false` in your solution file for the `stubs` target if you don't want your filled-in implementations overwritten on regeneration. The [api](/template-rust/docs/features/api.md) and [core](/template-rust/docs/features/api.md#data-types-core) files are always regenerated. ## Tests[​](#tests "Direct link to Tests") For each interface a unit test file is generated, like `📜tests/implementation_hello_test.rs`. It instantiates the default implementation and exercises each operation through both the `ApiFuture` form and the `_async` companion. These tests run as part of `cargo test` (no broker required) and are a starting point for your own tests: ``` #[tokio::test] async fn test_say() { let test_object = Hello::default(); let result = test_object.say(&Default::default(), Default::default()).await; assert!(result.is_ok()); let result_async = test_object.say_async(&Default::default(), Default::default()).await; assert!(result_async.is_ok()); } ``` --- # Template Rust This is the documentation for the *rust* template for the [ApiGear](/docs/guide/quick-start.md) code generator. It is split in several parts: * [Quick-Start](/template-rust/docs/quickstart.md?current-template=template-rust) is the easiest way to get started * [Features](/template-rust/docs/features.md) explains the available code generator features and their usage and purpose. --- # Quick-Start The Quick-Start guide explains how in a few steps you get from an API definition to a functional *Rust* example. Steps one and two are universal for other technologies. In step two you will choose a concrete *rust* template. For more general information about first steps with ApiGear see [First Steps](/docs/guide/quick-start.md). The quick start enables only basic features: the [api](/template-rust/docs/features/api.md) generation and a ready-to-use [stub](/template-rust/docs/features/stubs.md) implementation. For all available features check the [overview](/template-rust/docs/features.md). ## 1. Install the Code Generator[​](#1-install-the-code-generator "Direct link to 1. Install the Code Generator") ApiGear provides two powerful tools: a Studio and a Command Line Interface (CLI). The Studio is a graphical tool for creating, editing API modules and projects, while the CLI is a command-line tool for generating code from API modules. Both offer comprehensive functionality, including SDK template management, API project creation, code generation, API monitoring, and API simulation. [Download Studio](https://github.com/apigear-io/studio/releases/latest) [Download CLI](https://github.com/apigear-io/cli/releases/latest) ### Install ApiGear Studio[​](#install-apigear-studio "Direct link to Install ApiGear Studio") ApiGear Studio is available for macOS, Windows, and Linux. You can download it from the [ApiGear Studio GitHub releases page](https://github.com/apigear-io/studio/releases/latest). For more detailed information on ApiGear Studio, refer to the dedicated [studio documentation](/docs/studio/intro.md). ### Install ApiGear CLI[​](#install-apigear-cli "Direct link to Install ApiGear CLI") The ApiGear CLI is a versatile command-line tool for generating code from API modules. It's available for macOS, Windows, and Linux. Download the latest version from the [ApiGear CLI GitHub releases page](https://github.com/apigear-io/cli/releases/latest). The CLI offers all the features available in the Studio. For more detailed information on the ApiGear CLI, consult the dedicated [CLI documentation](/docs/cli/intro.md). ## 2. Obtain the Template[​](#2-obtain-the-template "Direct link to 2. Obtain the Template") There are several methods to obtain the template: installation via the Studio, installation via the CLI, or cloning/downloading from GitHub. This step is optional The ApiGear Studio and the CLI detect the specified template in the [solution document](#solution-file) and install it automatically. Optional steps (click to expand) ### Installation via CLI[​](#installation-via-cli "Direct link to Installation via CLI") When using the CLI, only the highlighted line needs to be executed. You can verify the successful installation using the `template cache` command. * Unreal Engine * C++14 * Qt6 * Python ``` $ apigear template install apigear-io/template-unreal@v3.2.2 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-unreal@v3.2.2 | https://github.com/apigear-io/template-unreal.git | | v3.2.2 ... ``` ``` $ apigear template install apigear-io/template-cpp14@v3.6.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-cpp14@v3.6.0 | https://github.com/apigear-io/template-cpp14.git | | v3.6.0 ... ``` ``` $ apigear template install apigear-io/template-qtcpp@v0.4.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-qtcpp@v0.4.0 | https://github.com/apigear-io/template-qtcpp.git | | v0.4.0 ... ``` ``` $ apigear template install apigear-io/template-python@v1.0.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-python@v1.0.0 | https://github.com/apigear-io/template-python.git | | v1.0.0 ... ``` ### Installation via Studio[​](#installation-via-studio "Direct link to Installation via Studio") Installing the template through the Studio is straightforward: * Unreal Engine * C++14 * Qt6 * Python 1. Open an existing project or create a new one 2. Navigate to the `Templates` tab 3. Click `Install` on the `apigear-io/template-unreal` entry 1) Open an existing project or create a new one 2) Navigate to the `Templates` tab 3) Click `Install` on the `apigear-io/template-cpp14` entry 1. Open an existing project or create a new one 2. Navigate to the `Templates` tab 3. Click `Install` on the `apigear-io/template-qtcpp` entry 1) Open an existing project or create a new one 2) Navigate to the `Templates` tab 3) Click `Install` on the `apigear-io/template-python` entry ![Installing the template](/img/apigear-studio-install-unreal-template.png) Figure: Installing the template ### Cloning from GitHub[​](#cloning-from-github "Direct link to Cloning from GitHub") If you need to inspect or modify the template's source code, cloning or downloading the repository is recommended. The repository doesn't need to be part of your project and can be stored anywhere on your computer. * Unreal Engine * C++14 * Qt6 * Python ``` $ git clone https://github.com/apigear-io/template-unreal.git ``` ``` $ git clone https://github.com/apigear-io/template-cpp14.git ``` ``` $ git clone https://github.com/apigear-io/template-qtcpp.git ``` ``` $ git clone https://github.com/apigear-io/template-python.git ``` You can then configure the solution file to use your template by providing the relative path from the solution file to the template directory. ## 3. Set Up the Project[​](#3-set-up-the-project "Direct link to 3. Set Up the Project") A typical project requires two files: a solution file specifying the APIs and the template to use, and at least one API module file. Ideally, both should be placed in a folder named `apigear`, adjacent to each other. Alternatively, you can use the Studio to create a new project and modify the two example files provided. ### Solution File[​](#solution-file "Direct link to Solution File") Create a [solution](/docs/guide/quick-start.md#solution-file) file. The example below specifies: * Module files in *line 8*, here the `helloworld.module.yaml` module with the `Hello` API * The output directory for generated files in *line 9* * The template used to generate the code in *line 10* (this can also be a path to a local copy of the template) * The enabled features of the template in *line 13*, here the `stubs` feature, which provides a simple implementation of interfaces. - Unreal Engine - C++14 - Qt6 - Python helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: ue_docs inputs: - helloworld.module.yaml output: ../ue_docs template: apigear-io/template-unreal@v3.2.2 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: cpp_hello_world inputs: - helloworld.module.yaml output: ../cpp_hello_world template: apigear-io/template-cpp14@v3.6.0 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: qt_hello_world inputs: - helloworld.module.yaml output: ../qt_hello_world template: apigear-io/template-qtcpp@v0.4.0 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: hello_world inputs: - helloworld.module.yaml output: ../py_hello_world template: apigear-io/template-python@v1.0.0 force: true features: - stubs ``` Targets You can extend this solution file with additional targets, each for a different technology with a different template. The `module.yaml` file is technology-independent and can be used with any template. Template Version We highly recommend specifying the exact version of the template. Otherwise, a newer version will be automatically downloaded and used for code generation. This can lead to unexpected behavior if file structures or code changes in the template. note Set the `force` parameter to `true` if you want to always override all generated files. When set to `false`, some files, such as implementations (stub feature), won't be updated. API files are always updated regardless of this setting. ### API Module File[​](#api-module-file "Direct link to API Module File") Use your preferred text editor to create the `helloworld.module.yaml` file with the following example content: helloworld.module.yaml ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` ## 4. Generate Code[​](#4-generate-code "Direct link to 4. Generate Code") ### Generate via CLI[​](#generate-via-cli "Direct link to Generate via CLI") The following snippet demonstrates how to run the CLI and provides an example output: ``` $ apigear generate solution apigear/helloworld.solution.yaml 10:52:20 INF generated 21 files in 30ms. (20 write, 0 skip, 1 copy) topic=gen ``` * The `generate` command instructs the CLI to generate code * The `solution` parameter specifies that we want to process a solution file ### Generate via Studio[​](#generate-via-studio "Direct link to Generate via Studio") 1. Open the project 2. Navigate to the `Solutions` tab 3. Click `Run` on the `helloworld.solution.yaml` entry ![Generating code](/img/apigear-studio-generate-code.png) Figure: Generating code ## 5. Use the generated Cargo workspace[​](#5-use-the-generated-cargo-workspace "Direct link to 5. Use the generated Cargo workspace") The *rust* template generates a [Cargo](https://doc.rust-lang.org/cargo/) workspace. Each API module becomes its own member crate, and a shared `examples` crate ties them together with runnable programs. To build and run the code you need a working [Rust toolchain](https://www.rust-lang.org/tools/install) (`rustc` and `cargo`, 1.80 or newer). note Basic Rust knowledge is necessary. ### Project folder structure[​](#project-folder-structure "Direct link to Project folder structure") With the output directory set as in the example, both *ApiGear* files reside in an `apigear` subfolder next to the generated *Rust* workspace. In this case the folder structure should look similar to this: ``` 📂hello-world ┣ 📂apigear ┃ ┣ 📜helloworld.solution.yaml ┃ ┗ 📜helloworld.module.yaml ┣ 📂rust_hello_world ┃ ┣ 📜Cargo.toml # workspace manifest (lists every module crate) ┃ ┣ 📜rustfmt.toml ┃ ┣ 📂io_world # one crate per module ┃ ┃ ┣ 📜Cargo.toml ┃ ┃ ┣ 📂src ┃ ┃ ┃ ┣ 📜lib.rs ┃ ┃ ┃ ┣ 📂api # interface traits + Publisher + data structs/enums ┃ ┃ ┃ ┣ 📂core_types # property bundle + shared reference ┃ ┃ ┃ ┗ 📂implementation # ready-to-use default impl ┃ ┃ ┗ 📂tests # per-interface unit tests ┃ ┗ 📂examples # runnable example programs ``` Using the solution file from the previous paragraph the code is generated in the `rust_hello_world` folder, with a subfolder (member crate) for each module — here `io_world`, the name of the module defined in line 2 of `helloworld.module.yaml`. It contains both generated features: the basic [api](/template-rust/docs/features/api.md) and a [stub](/template-rust/docs/features/stubs.md) implementation. The `io_world/src/api/` folder contains the trait definitions for your interfaces along with the `serde`-enabled enums and structs for your module, while `io_world/src/core_types/` holds the per-interface support types (a property bundle for state sync and a shared-reference helper). The `io_world/src/implementation/` folder holds a default implementation you can use as-is or build on. tip Check our `examples` crate with all features enabled to get more working examples — including IPC clients and servers for [OLink](/template-rust/docs/features/olink.md), [MQTT](/template-rust/docs/features/mqtt.md) and [NATS](/template-rust/docs/features/nats.md). note For the simulation, check [the olink feature](/template-rust/docs/features/olink.md) which provides a middle layer on your code side, and the [simulation](/docs/scripting/backends/intro.md) explained. ### Build the workspace[​](#build-the-workspace "Direct link to Build the workspace") Open a terminal, navigate to the generated `rust_hello_world` folder and build everything with Cargo: ``` cargo build ``` `cargo` resolves the workspace from the top-level `Cargo.toml`, downloads the dependencies, and compiles every module crate together with the `examples` crate. ### Run an example[​](#run-an-example "Direct link to Run an example") The generated `examples` crate ships a runnable program that exercises every interface in-process — calling operations, setting properties and listing available signals. Run it with: ``` cargo run -p rust_hello_world_examples ``` note The example crate's package name is derived from your solution target, with an `_examples` suffix. If your target is named `rust_hello_world`, the package is `rust_hello_world_examples`. Check the `[package] name` in `examples/Cargo.toml` and run `cargo run -p ` accordingly. ### Run the tests[​](#run-the-tests "Direct link to Run the tests") Each interface comes with a generated unit test that instantiates the default implementation and exercises its operations and properties. Run them with: ``` cargo test ``` From now on you can simply add your module crate to your own `Cargo.toml` as a dependency and use the generated traits, data types and default implementation. For more details on the generated features please check [api](/template-rust/docs/features/api.md) and [stubs](/template-rust/docs/features/stubs.md). --- # Features This guide explains how to use the generated code, what features are available, and their benefits. info A feature is a part of the template that generates a specific aspect of the code. For example, the `api` feature generates the core API interfaces and the `stubs` feature generates a stub implementation for the API. ## Get started[​](#get-started "Direct link to Get started") This template generates Unreal Engine plugins from your API definitions. The generated code integrates seamlessly with both Blueprints and C++. note Basic Unreal Engine knowledge is recommended. Familiarity with Unreal's plugin system, Blueprints, and C++ module structure will help you get the most out of the generated code. ### Code generation[​](#code-generation "Direct link to Code generation") Follow the documentation for [code generation](/docs/guide/quick-start.md) in general and [CLI](/docs/cli/generate.md) or the [Studio](/docs/studio/intro.md) tools. Or try first the [quick start guide](/template-unreal/docs/quickstart.md) which shows how to prepare an API and generate code from it. tip For questions regarding the template please go to our [discussions page](https://github.com/orgs/apigear-io/discussions). For feature requests or bug reports please use our [issue tracker](https://github.com/apigear-io/template-unreal/issues). ### Example API[​](#example-api "Direct link to Example API") The following code snippet contains the *API* definition which is used throughout this guide to demonstrate the generated code and its usage. Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` ## Features[​](#features-1 "Direct link to Features") The template provides features across three layers: **Core** (API and implementations), **Extended** (connectivity and monitoring), and **Infrastructure** (plugin support). These features compose to create a complete plugin architecture: *Your application programs against the generated API interfaces. Stubs provide local implementations, OLink, MsgBus, and MQTT connect to remote services, JNI bridges to Android services, Monitor wraps any implementation for tracing, and the Infrastructure layer provides settings and connection management.* note The JNI feature requires the `template-java` `jnibridge` feature for the Java-side bridge code. See [JNI](/template-unreal/docs/features/jni.md) for setup details. ### Core Features[​](#core-features "Direct link to Core Features") Core features generate Unreal Engine plugins from your API definition: * [api](/template-unreal/docs/features/api.md) - generates the core interfaces, data types (structs, enums), and abstract base classes. Provides both Blueprint-compatible interfaces and native C++ access. * [stubs](/template-unreal/docs/features/stubs.md) - generates ready-to-use implementation stubs as GameInstance Subsystems. Provides a starting point for your business logic and test fixtures for unit testing. ### Extended Features[​](#extended-features "Direct link to Extended Features") Extended features add connectivity and monitoring capabilities: * [olink](/template-unreal/docs/features/olink.md) - provides client and server adapters for the [ObjectLink](/docs/protocols/objectlink/intro.md) protocol. Use this to connect your Unreal application to remote services or the ApiGear simulation tools. * [msgbus](/template-unreal/docs/features/msgbus.md) - provides adapters using Unreal's built-in Message Bus for inter-process communication within the Unreal ecosystem. * [mqtt](/template-unreal/docs/features/mqtt.md) - provides client and adapter implementations on top of the [MQTT](https://mqtt.org/) pub/sub protocol. Use this to integrate with IoT brokers, Python or web peers, and many-to-many topologies. > **Choosing between MQTT, OLink, and Message Bus?** See the [comparison guide](/template-unreal/docs/features/mqtt.md#when-to-use-mqtt-vs-olink-vs-message-bus) for a detailed breakdown. * [jni](/template-unreal/docs/features/jni.md) - provides JNI adapter and client for bridging Unreal Engine and Android services. Requires the `template-java` `jnibridge` feature for the Java-side code. * [monitor](/template-unreal/docs/features/monitor.md) - generates a middleware layer which logs all API events to the [CLI](/docs/cli/intro.md) or the [Studio](/docs/studio/intro.md). ### Test Features[​](#test-features "Direct link to Test Features") * `olink_tests` - test fixtures and specs for OLink client/server functionality. * `msgbus_tests` - test fixtures and specs for Message Bus adapters. * `mqtt_tests` - test fixtures and specs for MQTT client/adapter functionality (uses an in-process loopback broker). * `jni_tests` - test fixtures and specs for JNI adapter/client functionality. ### Internal Features[​](#internal-features "Direct link to Internal Features") These features are generated automatically when required by other features: * `plugin` - generates the `{Module}Core` and `{Module}Editor` modules: * **Core module** (`IoWorldCore`): Settings class, implementation factory, and test utilities * **Editor module** (`IoWorldEditor`): Project Settings UI customization * `apigear` - core ApiGear plugin with connection management, settings, and editor UI * `apigear_olink` - OLink protocol support with client/host connections * `apigear_olinkproto` - ObjectLink protocol library * `apigear_mqtt` - transport-agnostic MQTT layer (`IApiGearMqttClient` strategy seam, `UApiGearMQTTClient`, `UApiGearMQTTHost`, in-process loopback broker for tests). Auto-enabled by `mqtt`. * `apigear_mqtt_paho` - production MQTT backend built on the [Eclipse Paho C async client](https://github.com/eclipse-paho/paho.mqtt.c). Bundles the `ThirdParty/PahoMQTTLibrary` module which auto-clones and CMake-builds Paho v1.3.14 on first build (requires `git` and `cmake` on `PATH`). Auto-enabled by `mqtt`. note You normally only list `mqtt` (and optionally `mqtt_tests`) in your solution's `features:` array — the template's rules pull `apigear_mqtt` and `apigear_mqtt_paho` in automatically. List them explicitly only if you need finer control (for example, generating just the transport-agnostic layer without the Paho backend). **Module Settings**: When you enable extended features, the Core module's settings class (`UIoWorldSettings`) gains configuration options accessible in Project Settings: | Setting | Feature | Purpose | | --------------------------- | ------- | --------------------------------------------------------- | | `TracerServiceIdentifier` | monitor | Select which backend implementation the monitor traces | | `OLinkConnectionIdentifier` | olink | Select which OLink connection the client uses | | `MsgBusHeartbeatIntervalMS` | msgbus | Configure heartbeat interval for service discovery | | `MQTTConnectionIdentifier` | mqtt | Select which MQTT connection (broker URL) the client uses | **Test Utilities**: The Core module includes test helpers for writing your own automation tests: ``` #include "IoWorld/Tests/IoWorldTestsCommon.h" FIoWorldMessage TestMsg = createTestFIoWorldMessage(); TArray TestArray = createTestFIoWorldMessageArray(); ``` ### Licensing and Author Metadata[​](#licensing-and-author-metadata "Direct link to Licensing and Author Metadata") All generated plugins include MIT LICENSE files and SPDX license headers. You can customize author information in `.uplugin` descriptors and copyright notices. See the [licensing and author metadata](/template-unreal/docs/features/licensing.md) guide for details. Each feature can be selected using the solution file or via the command line tool. note *Features are case sensitive, make sure to always **use lower-case.*** tip The *meta* feature `all` enables all specified features of the template. If you want to see the full extent of the generated code, `all` is the easiest solution. ## Folder structure[​](#folder-structure "Direct link to Folder structure") This graph shows the folder structure generated for a module with all features enabled. Each ApiGear module becomes an Unreal plugin. ``` 📂ue_project/Plugins ┣ 📂ApiGear ┃ ┣ 📜apigear.uplugin ┃ ┣ 📜LICENSE ┃ ┗ 📂Source ┃ ┣ 📂ApiGear ┃ ┣ 📂ApiGearEditor ┃ ┣ 📂ApiGearMQTT ┃ ┣ 📂ApiGearMQTTPaho ┃ ┣ 📂ApiGearOLink ┃ ┗ 📂ThirdParty ┃ ┣ 📂nlohmannJsonLibrary ┃ ┣ 📂OLinkProtocolLibrary ┃ ┗ 📂PahoMQTTLibrary ┣ 📂IoWorld ┃ ┣ 📜IoWorld.uplugin ┃ ┣ 📜LICENSE ┃ ┣ 📂Config ┃ ┗ 📂Source ┃ ┣ 📂IoWorldAPI ┃ ┣ 📂IoWorldCore ┃ ┣ 📂IoWorldEditor ┃ ┣ 📂IoWorldImplementation ┃ ┣ 📂IoWorldJni ┃ ┣ 📂IoWorldMonitor ┃ ┣ 📂IoWorldMQTT ┃ ┣ 📂IoWorldMsgBus ┃ ┗ 📂IoWorldOLink ``` note The module name `io.world` is converted to PascalCase `IoWorld` for Unreal naming conventions. Each feature generates a separate Unreal module within the plugin. --- # API Feature The `api` feature is the foundation for code generation. It generates: * Unreal interfaces (`UInterface`) for each API interface * Abstract base classes with property storage and change notification * Data types: `USTRUCT` for structs and `UENUM` for enumerations * Blueprint-compatible property accessors and operations * Signal delegates for reactive programming note The `api` feature generates interface definitions only. For a working implementation, also enable the [stubs](/template-unreal/docs/features/stubs.md) feature. ## Files overview per module[​](#files-overview-per-module "Direct link to Files overview per module") Using the example API definition: Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` The following file structure is generated in the `IoWorldAPI` module: ``` 📂IoWorld/Source/IoWorldAPI ┣ 📂Private ┃ ┣ 📂Generated ┃ ┃ ┣ 📂api ┃ ┃ ┃ ┣ 📜AbstractIoWorldHello.cpp ┃ ┃ ┃ ┣ 📜IoWorld_data.cpp ┃ ┃ ┃ ┗ 📜IoWorldHelloBPAdapter.cpp ┃ ┃ ┣ 📜IoWorldAPI.cpp ┃ ┃ ┗ 📜IoWorldLogCategories.cpp ┃ ┗ 📂IoWorld ┃ ┗ 📂Generated ┃ ┗ 📂api ┃ ┗ 📜IoWorldHelloPublisher.cpp ┣ 📂Public ┃ ┗ 📂IoWorld ┃ ┗ 📂Generated ┃ ┣ 📂api ┃ ┃ ┣ 📜IoWorld_apig.h ┃ ┃ ┣ 📜IoWorld_data.h ┃ ┃ ┣ 📜IoWorldHelloInterface.h ┃ ┃ ┣ 📜IoWorldHelloBPInterface.h ┃ ┃ ┣ 📜IoWorldHelloBPHelperInterface.h ┃ ┃ ┣ 📜IoWorldHelloBPAdapter.h ┃ ┃ ┗ 📜AbstractIoWorldHello.h ┃ ┣ 📜IoWorldAPI.h ┃ ┗ 📜IoWorldLogCategories.h ┗ 📜IoWorldAPI.Build.cs ``` note Private also contains `IoWorldHelloLatentAction.h` for Blueprint async operations support. ## Enums and Structures[​](#enums-and-structures "Direct link to Enums and Structures") The file `IoWorld_data.h` contains all data types defined in your API module. ### Enums[​](#enums "Direct link to Enums") For each enum in your API, a `UENUM` is generated: ``` UENUM(BlueprintType) enum class EIoWorldWhen : uint8 { IWW_Now = 0 UMETA(Displayname = "Now"), IWW_Soon = 1 UMETA(Displayname = "Soon"), IWW_Never = 2 UMETA(Displayname = "Never") }; ``` The enum values are prefixed with the enum name initials (`IWW_` for `IoWorld::When`) to ensure uniqueness across modules. tip Enums are fully Blueprint-compatible and appear in dropdowns with their display names. ### Structs[​](#structs "Direct link to Structs") For each struct in your API, a `USTRUCT` is generated: ``` USTRUCT(BlueprintType) struct IOWORLDAPI_API FIoWorldMessage : public FTableRowBase { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ApiGear|IoWorld") FString content{FString()}; FString ToJSON(bool bPrettyPrint = false) const; FString ToString() const; explicit operator FString() const; bool operator==(const FIoWorldMessage& rhs) const; bool operator!=(const FIoWorldMessage& rhs) const; }; ``` Each struct includes: * `FTableRowBase` inheritance for DataTable compatibility * `UPROPERTY` declarations for all fields with Blueprint read/write access and default initializers * JSON serialization (`ToJSON()`) and string conversion methods * Equality comparison operators ## Interfaces[​](#interfaces "Direct link to Interfaces") For each interface in your API module, several files are generated to support both C++ and Blueprint usage. ### Interface Definition (`IoWorldHelloInterface.h`)[​](#interface-definition-ioworldhellointerfaceh "Direct link to interface-definition-ioworldhellointerfaceh") The core interface as a Unreal `UInterface`. It includes the Publisher accessor, operations (synchronous, async with `TFuture`, and Blueprint latent), and property accessors: ``` UINTERFACE(BlueprintType, MinimalAPI, meta = (CannotImplementInterfaceInBlueprint)) class UIoWorldHelloInterface : public UInterface { GENERATED_BODY() }; class IOWORLDAPI_API IIoWorldHelloInterface { GENERATED_BODY() public: /// Provides access to the object which holds all the delegates /// this is needed since we cannot declare delegates on an UInterface /// @return object with signals for property state changes or standalone signals UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello") virtual UIoWorldHelloPublisher* _GetPublisher() = 0; // methods UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello|Operations", meta = (Latent, LatentInfo = "LatentInfo", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject")) virtual void SayAsync(UObject* WorldContextObject, FLatentActionInfo LatentInfo, int32& Result, const FIoWorldMessage& Msg, EIoWorldWhen When) = 0; virtual TFuture SayAsync(const FIoWorldMessage& Msg, EIoWorldWhen When) = 0; UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello|Operations") virtual int32 Say(const FIoWorldMessage& Msg, EIoWorldWhen When) = 0; // properties UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello|Properties") virtual FIoWorldMessage GetLast() const = 0; UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello|Properties") virtual void SetLast(UPARAM(DisplayName = "Last") const FIoWorldMessage& InLast) = 0; }; ``` The interface uses a **Publisher** pattern for reactive updates. The Publisher object (`UIoWorldHelloPublisher`) contains all delegates for signals and property changes. ### Properties[​](#properties "Direct link to Properties") Properties are exposed through getter/setter methods with `UFUNCTION(BlueprintCallable)` for Blueprint access. Each property also has change delegates (C++ and Blueprint variants) for reactive updates: ``` // property delegates DECLARE_MULTICAST_DELEGATE_OneParam(FIoWorldHelloLastChangedDelegate, const FIoWorldMessage& /* Last */); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FIoWorldHelloLastChangedDelegateBP, const FIoWorldMessage&, Last); ``` ### Operations[​](#operations "Direct link to Operations") Operations are generated with three variants to support different use cases: #### Synchronous (Blocking)[​](#synchronous-blocking "Direct link to Synchronous (Blocking)") The basic synchronous method blocks until completion: ``` virtual int32 Say(const FIoWorldMessage& Msg, EIoWorldWhen When) = 0; ``` #### Asynchronous with TFuture (C++)[​](#asynchronous-with-tfuture-c "Direct link to Asynchronous with TFuture (C++)") For non-blocking C++ code, use the `TFuture` variant: ``` virtual TFuture SayAsync(const FIoWorldMessage& Msg, EIoWorldWhen When) = 0; ``` Usage example: ``` // Non-blocking call - returns immediately TFuture Future = Hello->SayAsync(Msg, EIoWorldWhen::IWW_Now); // Option 1: Chain a callback Future.Next([](const int32& Result) { UE_LOG(LogTemp, Log, TEXT("Say returned: %d"), Result); }); // Option 2: Wait for result (blocks current thread) int32 Result = Future.Get(); // Option 3: Check if ready without blocking if (Future.IsReady()) { int32 Result = Future.Get(); } ``` #### Asynchronous with Latent Actions (Blueprints)[​](#asynchronous-with-latent-actions-blueprints "Direct link to Asynchronous with Latent Actions (Blueprints)") For Blueprint support, a latent action version is generated: ``` virtual void SayAsync(UObject* WorldContextObject, FLatentActionInfo LatentInfo, int32& Result, const FIoWorldMessage& Msg, EIoWorldWhen When) = 0; ``` This appears as an async node in Blueprints with execution pins for completion. The result is available when the output execution pin fires. tip Use async variants for: * Network operations ([OLink](/template-unreal/docs/features/olink.md), [MsgBus](/template-unreal/docs/features/msgbus.md)) to avoid blocking the game thread * Long-running computations * Operations that may take variable time ### Signals[​](#signals "Direct link to Signals") Signals are events broadcast from the interface. Each signal has both C++ and Blueprint delegate variants: ``` // signal delegates DECLARE_MULTICAST_DELEGATE_OneParam(FIoWorldHelloJustSaidDelegate, const FIoWorldMessage& /* Msg */); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FIoWorldHelloJustSaidDelegateBP, const FIoWorldMessage&, Msg); ``` The Publisher class (`UIoWorldHelloPublisher`) contains all signal and property change delegates. Access it through the interface: ``` UIoWorldHelloPublisher* Publisher = Hello->_GetPublisher(); Publisher->OnJustSaidSignalBP.AddDynamic(this, &UMyClass::OnJustSaid); Publisher->OnLastChangedBP.AddDynamic(this, &UMyClass::OnLastChanged); ``` The Publisher also supports batch subscription via subscriber interfaces for classes that need to handle multiple events. #### Subscribing to Events[​](#subscribing-to-events "Direct link to Subscribing to Events") **Using delegates directly:** ``` // For Blueprint-compatible binding (UFUNCTION required) Hello->_GetPublisher()->OnJustSaidSignalBP.AddDynamic(this, &UMyClass::OnJustSaid); Hello->_GetPublisher()->OnLastChangedBP.AddDynamic(this, &UMyClass::OnLastChanged); // For C++ raw binding (no UFUNCTION needed) Hello->_GetPublisher()->OnJustSaidSignal.AddRaw(this, &UMyClass::OnJustSaid); Hello->_GetPublisher()->OnLastChanged.AddLambda([](const FIoWorldMessage& Last) { // Handle property change }); ``` **Using subscriber interfaces (recommended for multiple events):** ``` // Implement IIoWorldHelloSubscriberInterface in your class class UMyListener : public UObject, public IIoWorldHelloSubscriberInterface { virtual void OnJustSaidSignal(const FIoWorldMessage& Msg) override; virtual void OnLastChanged(const FIoWorldMessage& Last) override; }; // Subscribe to all events at once TWeakInterfacePtr Subscriber(this); Hello->_GetPublisher()->Subscribe(Subscriber); // Later: Unsubscribe Hello->_GetPublisher()->Unsubscribe(Subscriber); ``` warning Always remove delegate bindings before the subscribing object is destroyed to avoid dangling references. ``` Hello->_GetPublisher()->OnJustSaidSignalBP.RemoveDynamic(this, &UMyClass::OnJustSaid); ``` ## Abstract Base Class[​](#abstract-base-class "Direct link to Abstract Base Class") The `UAbstractIoWorldHello` class provides a partial implementation: * **Lifecycle management**: Inherits from `UGameInstanceSubsystem` for automatic initialization/cleanup * **Property storage**: Protected `UPROPERTY` members with Blueprint getters/setters * **Publisher**: Manages signals and property change notifications * **Async support**: Default implementations that execute synchronous operations on a thread pool * **Pure virtual operations**: You must implement the synchronous operation methods To create your own implementation, extend this class and implement the pure virtual methods. Alternatively, enable the [stubs](/template-unreal/docs/features/stubs.md) feature to generate a ready-to-use implementation. note The async implementations safely handle object destruction using weak pointers, preventing crashes if the object is destroyed while an async operation is in progress. ## Blueprint Integration[​](#blueprint-integration "Direct link to Blueprint Integration") The generated interface is fully Blueprint-compatible: * All properties and operations are exposed as Blueprint-callable functions via `UFUNCTION` macros * Signals and property changes can be bound using Blueprint event dispatchers * Async operations work as latent nodes in Blueprint graphs ### Using in Blueprints[​](#using-in-blueprints "Direct link to Using in Blueprints") 1. **Get the subsystem**: Use "Get Game Instance Subsystem" and select your interface type 2. **Read properties**: Call the getter function (e.g., `Get Last`) 3. **Write properties**: Call the setter function (e.g., `Set Last`) 4. **Call operations**: Call operation functions directly (e.g., `Say`) 5. **React to changes**: Bind to the property changed or signal delegates ## Using in C++[​](#using-in-c "Direct link to Using in C++") ### Module Dependency[​](#module-dependency "Direct link to Module Dependency") Add the API module to your `.Build.cs`: ``` PublicDependencyModuleNames.AddRange(new string[] { "IoWorldAPI" }); ``` ### Include Headers[​](#include-headers "Direct link to Include Headers") ``` #include "IoWorld/Generated/api/IoWorldHelloInterface.h" #include "IoWorld/Generated/api/IoWorld_data.h" ``` ### Access the Interface[​](#access-the-interface "Direct link to Access the Interface") Through a subsystem (requires [stubs](/template-unreal/docs/features/stubs.md) feature): ``` #include "IoWorld/Implementation/IoWorldHello.h" TScriptInterface Hello = GetGameInstance()->GetSubsystem(); // Read property FIoWorldMessage LastMsg = Hello->GetLast(); // Write property FIoWorldMessage NewMsg; NewMsg.content = TEXT("Hello World"); Hello->SetLast(NewMsg); // Call operation int32 Result = Hello->Say(NewMsg, EIoWorldWhen::IWW_Now); ``` ### Subscribe to Changes[​](#subscribe-to-changes "Direct link to Subscribe to Changes") **Using lambdas (simplest approach):** ``` // Subscribe with lambda - no class method needed Hello->_GetPublisher()->OnLastChanged.AddLambda([](const FIoWorldMessage& Last) { UE_LOG(LogTemp, Log, TEXT("Last changed to: %s"), *Last.content); }); Hello->_GetPublisher()->OnJustSaidSignal.AddLambda([this](const FIoWorldMessage& Msg) { // Can capture 'this' if needed - be careful of object lifecycle HandleMessage(Msg); }); ``` **Using member functions (C++ delegates):** ``` // In your class header - regular C++ method, no UFUNCTION needed void OnLastChanged(const FIoWorldMessage& Last); void OnJustSaid(const FIoWorldMessage& Msg); // In implementation - bind to C++ delegates Hello->_GetPublisher()->OnLastChanged.AddRaw(this, &UMyClass::OnLastChanged); Hello->_GetPublisher()->OnJustSaidSignal.AddUObject(this, &UMyClass::OnJustSaid); ``` **Using Blueprint-compatible delegates (requires UFUNCTION):** ``` // In your class header UFUNCTION() void OnLastChanged(const FIoWorldMessage& Last); // In implementation - bind to BP delegates Hello->_GetPublisher()->OnLastChangedBP.AddDynamic(this, &UMyClass::OnLastChanged); ``` caution Always remove delegate bindings when your object is destroyed to prevent crashes: ``` // For C++ delegates Hello->_GetPublisher()->OnLastChanged.RemoveAll(this); // For Blueprint delegates Hello->_GetPublisher()->OnLastChangedBP.RemoveDynamic(this, &UMyClass::OnLastChanged); ``` ## Other Files[​](#other-files "Direct link to Other Files") * `📜IoWorldAPI.Build.cs` - Unreal build configuration for the module, including dependencies * `📜IoWorld_apig.h` - Module convenience header that includes all API headers * `📜IoWorldLogCategories.h/cpp` - Log category definitions for the module * `📜IoWorldHelloPublisher.cpp` - Publisher implementation for signals and property changes --- # JNI The `jni` feature provides adapter and client components for Android service communication via JNI. This enables: * **Expose your Unreal implementation** as an Android service that other Android apps can bind to * **Consume Android services** from your Unreal application * **Bridge between Unreal Engine C++ and Android Java** using the Messenger IPC protocol Use a *JNI client* to connect your Unreal application to an Android service. Use a *JNI adapter* to expose your local implementation as an Android service. tip The primary use case is the **client side**. Unreal Engine applications typically act as rendering frontends, consuming data from native Android services that own and share the data. The adapter side is available for cases where the Unreal application needs to expose its implementation to other Android apps. note The JNI feature requires the **template-java** `jnibridge` feature to generate the Java-side JNI bridge code. Both templates must target the same API definition. caution The JNI feature requires your Unreal Engine project to be set up for Android development. If you haven't done this yet, follow the [Unreal Engine Android setup guide](https://dev.epicgames.com/documentation/en-us/unreal-engine/setting-up-unreal-engine-projects-for-android-development) before proceeding. ## Architecture[​](#architecture "Direct link to Architecture") The JNI feature connects Unreal Engine to Android through two generated layers. Template-java generates the Java side, which manages Android service lifecycle and Messenger IPC. This template generates the C++ side, which provides the JNI bindings Unreal Engine uses to communicate with that Java layer. **Client path** (Unreal app consumes an Android service): **Adapter path** (Unreal app exposes an Android service): *You typically use only one side per application. See the tip above for guidance on which side to use.* On the Android side, cross-process communication follows the [Messenger](https://developer.android.com/reference/android/os/Messenger) bound-service pattern. The JNI layer translates between C++ and Java; Messenger handles the actual IPC between applications. For details on the Java-side implementation, refer to the [template-java](/template-java/docs/intro.md) documentation. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") ### Solution File Setup[​](#solution-file-setup "Direct link to Solution File Setup") Your solution file must include both the Unreal and Java template layers targeting the same API: ``` schema: "apigear.solution/1.0" name: helloworld version: "0.1.0" layers: - name: unreal inputs: - helloworld.module.yaml output: ../YourProject/Plugins template: apigear-io/template-unreal features: [api, stubs, jni, plugin] - name: java inputs: - helloworld.module.yaml output: ../YourProject/android template: apigear-io/template-java features: [api, android, stubs, jnibridge] ``` The Java modules must be output to an `android/` directory next to the `Plugins/` directory. The Unreal build system compiles them together with the JNI module. ### Platform Requirements[​](#platform-requirements "Direct link to Platform Requirements") * Android SDK version 33 or higher * Unreal Engine 5.5 or later * Android NDK (provided by UE Android setup) ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With our example API definition: Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` The following file structure is generated: ``` 📂IoWorld/Source/IoWorldJni ┣ 📂Public/IoWorld ┃ ┣ 📜IoWorldJni.h ┃ ┗ 📂Generated/Jni ┃ ┣ 📜IoWorldHelloJniAdapter.h ┃ ┣ 📜IoWorldHelloJniClient.h ┃ ┣ 📜IoWorldDataJavaConverter.h ┃ ┗ 📜IoWorldJniConnectionStatus.h ┣ 📂Private/Generated ┃ ┣ 📂Detail ┃ ┣ 📂Jni ┃ ┃ ┣ 📜IoWorldHelloJniAdapter.cpp ┃ ┃ ┣ 📜IoWorldHelloJniClient.cpp ┃ ┃ ┗ 📜IoWorldDataJavaConverter.cpp ┃ ┗ 📜IoWorldJni.cpp ┣ 📜IoWorldJni.Build.cs ┗ 📜IoWorld_JNI_UPL.xml ``` The `IoWorld_JNI_UPL.xml` is an Unreal Plugin Language file that configures Android manifest entries, permissions, and Java source inclusion for the build. ## JNI Adapter (Service Side)[​](#jni-adapter-service-side "Direct link to JNI Adapter (Service Side)") The `UIoWorldHelloJniAdapter` exposes a local `IIoWorldHelloInterface` implementation as an Android service. Other Android applications can bind to this service via Android's [Messenger](https://developer.android.com/reference/android/os/Messenger) IPC mechanism. Each interface declared in your `*.module.yaml` produces a separate Android service. All services run on the application thread — no extra threads are spawned per service. ### How It Works[​](#how-it-works "Direct link to How It Works") 1. **Wraps local implementation**: Takes an existing implementation (e.g., your stub) via `setBackendService()` 2. **Creates Android service**: On initialization, starts a Java Android service with the JNI adapter as its native backend 3. **Forwards changes**: Property changes and signals from the C++ implementation are forwarded to bound Java clients via JNI callbacks 4. **Handles remote requests**: Incoming requests from Java clients are forwarded to the wrapped implementation *The adapter wraps any `IIoWorldHelloInterface` implementation and exposes it as an Android service. Multiple Android clients can bind simultaneously.* ### Thread Safety[​](#thread-safety "Direct link to Thread Safety") The adapter marshals all incoming JNI calls to the **game thread**, so your backend implementation never receives calls from the JNI thread directly. ### Delegates[​](#delegates "Direct link to Delegates") | Delegate | Description | | -------------------- | ------------------------------------------------ | | `_JniServiceStarted` | Fires when the Android service has fully started | | `_JniServiceDied` | Fires when the Android service dies unexpectedly | Both delegates have Blueprint-assignable variants (`_JniServiceStartedBP`, `_JniServiceDiedBP`). ### Using the JNI Adapter[​](#using-the-jni-adapter "Direct link to Using the JNI Adapter") ``` #include "IoWorld/Generated/Jni/IoWorldHelloJniAdapter.h" #include "IoWorld/Implementation/IoWorldHello.h" // Get your local implementation (GameInstance subsystem) UIoWorldHelloImplementation* LocalHello = GetGameInstance()->GetSubsystem(); // Get the JNI adapter (GameInstance subsystem) UIoWorldHelloJniAdapter* Adapter = GetGameInstance()->GetSubsystem(); // Connect the adapter to your implementation Adapter->setBackendService(LocalHello); // Monitor service lifecycle Adapter->_JniServiceStarted.AddLambda([]() { UE_LOG(LogTemp, Log, TEXT("Android service started - clients can now bind")); }); Adapter->_JniServiceDied.AddLambda([]() { UE_LOG(LogTemp, Warning, TEXT("Android service died unexpectedly")); }); ``` The adapter initializes the Android service automatically. The `_JniServiceStarted` delegate fires asynchronously on the game thread once the Java-side Android service has fully started. Once it fires, remote Android clients can bind to the service. ## JNI Client[​](#jni-client "Direct link to JNI Client") The `UIoWorldHelloJniClient` connects to a remote Android service and implements `IIoWorldHelloInterface`. It can be used interchangeably with stubs, OLink clients, or any other implementation. ### How It Works[​](#how-it-works-1 "Direct link to How It Works") 1. **Bind to service**: Call `_bindToService()` with the target application's package name 2. **Use the interface**: Properties, operations, and signals work transparently through JNI 3. **Async operations**: Operations return `TFuture` for non-blocking execution *The client acts as a **remote proxy** — it implements the same interface as a local implementation but communicates with an Android service via JNI and Messenger IPC.* ### Connection Management[​](#connection-management "Direct link to Connection Management") ``` #include "IoWorld/Generated/Jni/IoWorldHelloJniClient.h" // Get the JNI client (GameInstance subsystem) UIoWorldHelloJniClient* HelloClient = GetGameInstance()->GetSubsystem(); // Bind to the service exposed by another Android app // servicePackage: the Android package name of the app hosting the service // connectionId: a unique identifier for this connection bool bBound = HelloClient->_bindToService(TEXT("com.example.myservice"), TEXT("connection1")); // Check connection state if (HelloClient->_IsReady()) { // Client is connected and ready } // React to connection changes HelloClient->_ConnectionStatusChanged.AddLambda([](bool bConnected) { UE_LOG(LogTemp, Log, TEXT("JNI connection: %s"), bConnected ? TEXT("ready") : TEXT("disconnected")); }); ``` caution The JNI client does not automatically start the remote service. The service must already be running before `_bindToService()` is called. Automatic reconnection is not provided — if the service dies, you must call `_bindToService()` again. ### Properties[​](#properties "Direct link to Properties") Properties are synchronized from the remote service. When the service-side implementation changes a property, the client receives the update via JNI callback: ``` // Getter returns the current value (updated from remote service) FIoWorldMessage UIoWorldHelloJniClient::GetLast() const; // Setter sends a change request to the remote service void UIoWorldHelloJniClient::SetLast(const FIoWorldMessage& InLast); ``` ### Operations[​](#operations "Direct link to Operations") Operations support both synchronous and asynchronous variants: ``` // Synchronous (blocks until the remote service responds) int32 Result = HelloClient->Say(Msg, EIoWorldWhen::IWW_Now); // Asynchronous (returns immediately) HelloClient->SayAsync(Msg, EIoWorldWhen::IWW_Now).Then([](TFuture Future) { int32 Result = Future.Get(); // Handle result }); ``` caution Synchronous calls block the calling thread while waiting for the remote service to respond. This includes JNI calls, Messenger IPC, and the service-side processing. **Prefer async variants to avoid blocking the game thread.** ### Signals[​](#signals "Direct link to Signals") Signals from the remote service are broadcast to local subscribers via the Publisher: ``` HelloClient->_GetPublisher()->OnJustSaidSignalBP.AddDynamic(this, &UMyClass::OnJustSaid); ``` ### Blueprint Usage[​](#blueprint-usage "Direct link to Blueprint Usage") The `_bindToService()` and `_unbind()` functions are Blueprint-callable. Connection status can be monitored via the `_ConnectionStatusChangedBP` delegate. Use the client through the `IIoWorldHelloInterface` like any other implementation. ## Data Conversion[​](#data-conversion "Direct link to Data Conversion") The `IoWorldDataJavaConverter` class handles automatic conversion between C++ and Java types across the JNI boundary. ### Automatic Conversion[​](#automatic-conversion "Direct link to Automatic Conversion") Structs, enums, arrays, and primitive types are converted automatically. No user action is required for these types. ### Manual Conversion Required[​](#manual-conversion-required "Direct link to Manual Conversion Required") * **Extern types**: Since ApiGear has no knowledge of extern type structure, automatic conversion is impossible. Only skeleton functions are generated — you **must** provide the conversion logic yourself in `IoWorldDataJavaConverter.cpp`: * `fillYourExternName()` — converts from Java object to C++ type * `makeJavaYourExternName()` — converts from C++ type to Java object ## Android Project Configuration[​](#android-project-configuration "Direct link to Android Project Configuration") The minimum target SDK version must be **33 or higher**, as required by the template-java side. When providing the service side, set the `PackageName` in your Android project settings — clients must specify this package name in `_bindToService()` to connect to your service. The `IoWorld_JNI_UPL.xml` file automatically configures the Android manifest with service declarations and query entries needed for service binding. ## Testing[​](#testing "Direct link to Testing") The `jni_tests` feature generates test fixtures for JNI functionality: ``` 📂IoWorld/Source/IoWorldJni/Private/Tests ┣ 📜IoWorldHelloJni.spec.cpp ┣ 📜IoWorldHelloJniFixture.h ┗ 📜IoWorldHelloJniFixture.cpp ``` These tests verify adapter and client functionality including property synchronization, operation invocation, and signal propagation. They require an Android device or emulator to run. ### Running Tests[​](#running-tests "Direct link to Running Tests") Install and launch your Unreal application on the target Android device. Once the application is running, trigger the tests via an adb broadcast: ``` adb shell am broadcast -a android.intent.action.RUN --es cmd '"Automation RunTests "' ``` Replace `` with your ApiGear module name in PascalCase (e.g., `IoWorld` for the `io.world` module). This runs all JNI tests for that module. Monitor test results using `adb logcat` — look for `LogAutomationController` entries. ## Best Practices[​](#best-practices "Direct link to Best Practices") ### Async Operations[​](#async-operations "Direct link to Async Operations") Prefer async operation variants to avoid blocking the game thread across the JNI and IPC boundary. See [Operations](#operations) for usage examples. If an async operation fails on the service side, the `TPromise` is fulfilled with a default-constructed value and a warning is logged. This prevents deadlocks from unfulfilled promises but means the caller cannot distinguish a failure from a legitimate default return value. Check logs if you suspect silent failures. ### Service Lifecycle[​](#service-lifecycle "Direct link to Service Lifecycle") * The client does not auto-reconnect — call `_bindToService()` again after the remote service restarts * Subscribe to `_ConnectionStatusChanged` (client) and `_JniServiceDied` (adapter) to detect disconnections and trigger recovery * On the adapter side, if `_JniServiceDied` fires, re-initialize the adapter by calling `setBackendService()` again to restart the Android service ### Performance Considerations[​](#performance-considerations "Direct link to Performance Considerations") * JNI calls involve serialization, JNI boundary crossing, and Messenger IPC — avoid calling them every frame * Use property change delegates for reactive updates instead of polling getters * Consider enabling monitor tracing only for specific interfaces, as it adds overhead on top of JNI ### Package Name Coordination[​](#package-name-coordination "Direct link to Package Name Coordination") * Both sides must agree on the Android package name for service binding * The client passes this name to `_bindToService()`; the adapter's app must be installed with that exact package name * Document the package name in your solution file or a shared project configuration to avoid mismatches ### Extern Type Converters[​](#extern-type-converters "Direct link to Extern Type Converters") * Implement extern type converters in `IoWorldDataJavaConverter.cpp` early in development * Unimplemented converter skeletons compile but silently return default-constructed values at runtime * The generated `makeJava*` functions assume the extern Java class has a no-argument constructor — if it does not, a `NoSuchMethodError` occurs at runtime * Ensure the extern type's Java class is loadable by the default class loader; classes from third-party libraries may require additional packaging configuration ### Interface Role Separation[​](#interface-role-separation "Direct link to Interface Role Separation") * Each application should use either the adapter or the client for a given interface, not both * The adapter exposes a local implementation as a service; the client consumes a remote one * Using both in the same app for the same interface creates an unnecessary round-trip through Messenger IPC back to itself --- # Licensing and Author Metadata Every generated plugin includes standardized licensing and copyright information. You can also inject your own author metadata into the generated `.uplugin` descriptors and LICENSE files. ## License Files[​](#license-files "Direct link to License Files") Each generated plugin gets a `LICENSE` file at its root containing the full MIT license text: ``` MIT License Copyright (c) 2020-2021 ApiGear UG (haftungsbeschraenkt) Copyright (c) 2021-2026 Epic Games Inc. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy ... ``` The ApiGear shared plugin (`Plugins/ApiGear/`) also receives a LICENSE file. ## SPDX Headers[​](#spdx-headers "Direct link to SPDX Headers") All generated source files include [SPDX license identifiers](https://spdx.dev/learn/handling-license-info/) instead of inline license blocks: ``` // SPDX-FileCopyrightText: Copyright ApiGear UG and Epic Games, Inc. // SPDX-License-Identifier: MIT ``` Auto-generated files (those regenerated on every run) also include a warning: ``` // SPDX-FileCopyrightText: Copyright ApiGear UG and Epic Games, Inc. // SPDX-License-Identifier: MIT // This file has been generated by the ApiGear Code Generator. // Do not edit. Changes will be lost when the code is re-generated. ``` User-editable files (stubs, test fixtures marked with `preserve: true`) get the SPDX header only, with no "do not edit" warning. note Third-party libraries bundled in the ApiGear plugin (OLinkProtocolLibrary, nlohmann/json) retain their original license headers. ## Author Metadata[​](#author-metadata "Direct link to Author Metadata") Use the `meta` field to set author information that appears in `.uplugin` descriptors and LICENSE files. ### Setting Author Info[​](#setting-author-info "Direct link to Setting Author Info") Specify `author` and `author_url` in your solution or module YAML files. **In the solution file** (applies to all modules): helloworld.solution.yaml ``` schema: apigear.solution/1.0 targets: - name: ue_docs output: ../ue_docs/Plugins template: apigear-io/template-unreal features: - all inputs: - helloworld.module.yaml meta: author: "Acme Corp" author_url: "https://acme.com" ``` **In a module file** (per-module override): helloworld.module.yaml ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" meta: author: "Acme Corp" author_url: "https://acme.com" interfaces: - name: Hello # ... ``` ### Priority[​](#priority "Direct link to Priority") Module-level `meta` takes priority over solution-level `meta`. This lets you set a default author in the solution file and override it for specific modules when needed. ### Generated Output[​](#generated-output "Direct link to Generated Output") When author metadata is set, it populates two locations: **`.uplugin` descriptor** -- the `CreatedBy` and `CreatedByURL` fields: IoWorld.uplugin ``` { "FileVersion": 3, "FriendlyName": "IoWorld", "CreatedBy": "Acme Corp", "CreatedByURL": "https://acme.com", ... } ``` **LICENSE file** -- an additional copyright line: ``` MIT License Copyright (c) 2020-2021 ApiGear UG (haftungsbeschraenkt) Copyright (c) 2021-2026 Epic Games Inc. All rights reserved. Copyright (c) Acme Corp. All rights reserved. Permission is hereby granted ... ``` When no `meta` is specified, `CreatedBy` and `CreatedByURL` are empty strings and no extra copyright line appears. This is the default behavior and is fully backward compatible. ### Meta Field Reference[​](#meta-field-reference "Direct link to Meta Field Reference") | Key | Type | Description | | ------------ | ------ | ------------------------------------------------------------------------------------ | | `author` | string | Author or organization name. Populates `.uplugin` `CreatedBy` and LICENSE copyright. | | `author_url` | string | Author website URL. Populates `.uplugin` `CreatedByURL`. | --- # Monitor The `monitor` feature generates tracing capabilities to observe your interfaces at runtime. It enables: * **Property tracking**: See when properties change and their new values * **Operation logging**: Track method calls with parameters and return values * **Signal monitoring**: Observe signal emissions and their data * **Live debugging**: View all activity in ApiGear Studio or CLI The monitoring server is integrated into [ApiGear Studio](/docs/studio/intro.md) and the [CLI](/docs/cli/intro.md). For more details on monitoring concepts, see [monitoring documentation](/docs/monitor/intro.md). ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With our example API definition: Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` The following file structure is generated: ``` 📂ApiGear/Source/ApiGear ┣ 📂Public ┃ ┗ 📜tracer.h ┗ 📂Private ┗ 📜tracer.cpp 📂IoWorld/Source/IoWorldMonitor ┣ 📂Private ┃ ┗ 📂Generated ┃ ┣ 📜IoWorldMonitor.cpp ┃ ┗ 📂Monitor ┃ ┣ 📜IoWorld.trace.h ┃ ┣ 📜IoWorld.trace.cpp ┃ ┗ 📜IoWorldHelloLoggingDecorator.cpp ┣ 📂Public ┃ ┗ 📂IoWorld ┃ ┣ 📜IoWorldMonitor.h ┃ ┗ 📂Generated/Monitor ┃ ┗ 📜IoWorldHelloLoggingDecorator.h ┗ 📜IoWorldMonitor.Build.cs ``` The `ApiGear` plugin provides the tracer client that connects to the monitoring server. Each module generates interface-specific trace helpers (in the `Monitor` subdirectory) and a logging decorator class that wraps implementations. ## Tracer[​](#tracer "Direct link to Tracer") The `ApiGear` plugin includes a tracer client that connects to the monitoring server and sends trace data. ### Tracer Configuration[​](#tracer-configuration "Direct link to Tracer Configuration") Configure the tracer in your game initialization: ``` #include "tracer.h" UApiGearTracer* Tracer = GetGameInstance()->GetSubsystem(); Tracer->Connect(TEXT("ws://localhost:8182/ws"), TEXT("MyUnrealApp")); ``` ### Project Settings[​](#project-settings "Direct link to Project Settings") You can also configure tracing in Project Settings: 1. Open Project Settings > Plugins > ApiGear 2. Set the Tracer URL (e.g., `ws://localhost:8182/ws`) 3. Set the Application Name for identification 4. Enable/disable auto-connect ## Logging Decorator[​](#logging-decorator "Direct link to Logging Decorator") The `UIoWorldHelloLoggingDecorator` wraps any `IIoWorldHelloInterface` implementation and logs all interactions to the tracer. ### How it works[​](#how-it-works "Direct link to How it works") The decorator: 1. Wraps an existing interface implementation 2. Intercepts all property access, operation calls, and signal emissions 3. Sends trace data to the connected monitoring server 4. Forwards all calls to the wrapped implementation (transparent) The decorator extends `UAbstractIoWorldHello` and subscribes to notifications from the wrapped backend service. The key method for setup is: ``` // Set which implementation to wrap and trace void setBackendService(TScriptInterface InService); ``` Once configured, use the decorator exactly like any other implementation—it implements the full interface and forwards all calls to the backend while logging. note The monitor feature uses generated tracer helper classes (in `Private/`) that handle JSON serialization automatically. The logging decorator manages all tracing - you don't need to interact with these classes directly. ### What Gets Traced[​](#what-gets-traced "Direct link to What Gets Traced") The logging decorator automatically traces: * **Property writes**: When you call `SetLast()`, a `call` event is sent with the new value * **Property changes**: When the backend notifies of a change, the full interface state is captured * **Operations**: All operation calls are traced with their parameters * **Signals**: Signal emissions are traced with their data Property reads (`GetLast()`) are not traced since they don't modify state. ## Using the Monitor[​](#using-the-monitor "Direct link to Using the Monitor") ### Basic Setup[​](#basic-setup "Direct link to Basic Setup") The logging decorator is a GameInstance Subsystem, so you access it via `GetSubsystem<>()` and configure which implementation it wraps. You can wrap any implementation, including network clients. ``` #include "tracer.h" #include "IoWorld/Generated/Monitor/IoWorldHelloLoggingDecorator.h" #include "IoWorld/Implementation/IoWorldHello.h" UIoWorldHelloImplementation* HelloImpl = GetGameInstance()->GetSubsystem(); UIoWorldHelloLoggingDecorator* TracedHello = GetGameInstance()->GetSubsystem(); TracedHello->setBackendService(HelloImpl); TScriptInterface Hello = TracedHello; Hello->Say(Msg, EIoWorldWhen::IWW_Now); ``` ### Settings Integration[​](#settings-integration "Direct link to Settings Integration") Instead of manual setup, configure the backend service via Project Settings: 1. Open **Project Settings > Plugins > IoWorld** 2. Set **Tracer Service Identifier** to the backend you want to trace (e.g., `Local` for stubs, `OLink` for network) The decorator automatically wraps the configured backend during initialization. ## Best Practices[​](#best-practices "Direct link to Best Practices") ### Performance Considerations[​](#performance-considerations "Direct link to Performance Considerations") * Tracing adds overhead for serialization and network transmission * Disable in performance-critical sections if needed * Consider tracing only specific interfaces of interest --- # MQTT The `mqtt` feature provides client and adapter implementations on top of the [MQTT](https://mqtt.org/) pub/sub protocol. This enables: * **Broker-mediated connections**: Connect Unreal applications through any MQTT-compliant broker (Mosquitto, EMQX, HiveMQ, AWS IoT Core, etc.) * **Cross-platform fan-out**: Communicate with Python services, JavaScript dashboards, mobile apps, and embedded devices that all speak MQTT * **IoT and telemetry**: Stream data into and out of Unreal alongside the broader IoT ecosystem * **Many-to-many communication**: One adapter can serve many clients, and any client can subscribe to topics from many adapters Use an *MQTT client* instead of your local implementation to consume a remote service over MQTT. Use an *MQTT adapter* to expose your local implementation to MQTT subscribers. note Unlike OLink (point-to-point WebSocket) and Message Bus (intra-Unreal IPC), MQTT requires an external broker. The broker decouples publishers from subscribers and handles message routing, retention, and last-will semantics. ## When to use MQTT vs OLink vs Message Bus[​](#when-to-use-mqtt-vs-olink-vs-message-bus "Direct link to When to use MQTT vs OLink vs Message Bus") | Feature | MQTT | OLink | Message Bus | | ------------------ | ---------------------------------- | ---------------------------- | ------------------------------- | | Protocol | MQTT 5 over TCP / TLS | WebSocket (ObjectLink) | Unreal's UDP Message Bus | | Topology | Pub/sub via broker | Direct request/response | Local UDP discovery | | Best for | IoT, telemetry, many-to-many | Tight Unreal-to-service link | Unreal-to-Unreal IPC | | Setup | Requires broker URL | Requires server URL | Zero configuration on localhost | | Network | Any network with broker reachable | Any network | Local network / same machine | | Non-Unreal peers | Yes (Python, JS, mobile, embedded) | Yes (Qt, C++, simulation) | No | | Retained state | Yes (broker retains last property) | No (live link only) | No (heartbeat-based) | | ApiGear Simulation | No | Yes | No | For the full cross-language transport comparison (including NATS), see [Choosing a transport](/docs/protocols/mapping.md#choosing-a-transport). tip Use **MQTT** when fanning state out to many heterogeneous subscribers or integrating with an existing IoT topology. Use **OLink** for direct Unreal-to-service or simulation connections. Use **Message Bus** for IPC between Unreal applications. ## ApiGear MQTT Protocol[​](#apigear-mqtt-protocol "Direct link to ApiGear MQTT Protocol") The generated code maps interface concepts to MQTT topics following the shared ApiGear MQTT mapping. The topic structure, JSON payloads, MQTT 5 correlation (`CorrelationData` / `ResponseTopic`), and retained property notifications are documented in **[ApiGear over MQTT](/docs/protocols/mqtt/mapping.md)**. ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With our example API definition: Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` The following file structure is generated: ``` 📂ApiGear/Source ┣ 📂ApiGearMQTT ┃ ┣ 📂Private ┃ ┃ ┣ 📜apigearmqtt.cpp ┃ ┃ ┣ 📜ApiGearMQTTClient.cpp ┃ ┃ ┗ 📜ApiGearMQTTHost.cpp ┃ ┣ 📂Public ┃ ┃ ┣ 📜apigearmqtt.h ┃ ┃ ┣ 📜ApiGearMQTTClient.h ┃ ┃ ┣ 📜ApiGearMQTTHost.h ┃ ┃ ┣ 📜ApiGearMQTTTypes.h ┃ ┃ ┣ 📜IApiGearMqttClient.h ┃ ┃ ┗ 📜MqttLoopbackClient.h ┃ ┗ 📜apigearmqtt.Build.cs ┣ 📂ApiGearMQTTPaho ┃ ┣ 📂Private ┃ ┃ ┣ 📜ApiGearMQTTPaho.cpp ┃ ┃ ┗ 📜PahoMqttClient.cpp ┃ ┣ 📂Public ┃ ┃ ┣ 📜ApiGearMQTTPaho.h ┃ ┃ ┗ 📜PahoMqttClient.h ┃ ┗ 📜ApiGearMQTTPaho.Build.cs ┗ 📂ThirdParty/PahoMQTTLibrary 📂IoWorld/Source/IoWorldMQTT ┣ 📂Private ┃ ┗ 📂Generated ┃ ┣ 📜IoWorldMQTT.cpp ┃ ┗ 📂MQTT ┃ ┣ 📜IoWorldHelloMQTTClient.cpp ┃ ┗ 📜IoWorldHelloMQTTAdapter.cpp ┣ 📂Public ┃ ┗ 📂IoWorld ┃ ┣ 📜IoWorldMQTT.h ┃ ┗ 📂Generated/MQTT ┃ ┣ 📜IoWorldHelloMQTTClient.h ┃ ┗ 📜IoWorldHelloMQTTAdapter.h ┗ 📜IoWorldMQTT.Build.cs ``` ## ApiGear MQTT Network Layer[​](#apigear-mqtt-network-layer "Direct link to ApiGear MQTT Network Layer") The MQTT support is split into two infrastructure modules in the `ApiGear` plugin: ### ApiGearMQTT (transport-agnostic)[​](#apigearmqtt-transport-agnostic "Direct link to ApiGearMQTT (transport-agnostic)") `ApiGearMQTT` defines the abstract transport layer used by every generated MQTT client and adapter. It does not depend on any specific MQTT library: * **`IApiGearMqttClient`** — the abstract seam. Any MQTT library (Paho, Mqttify, MQTTCore, a mock) can be adapted by implementing this interface: ``` class APIGEARMQTT_API IApiGearMqttClient { public: virtual void Connect(const FString& URL) = 0; virtual void Disconnect() = 0; virtual bool IsConnected() const = 0; virtual FString GetClientId() const = 0; // FMessageCallback receives an FReceivedMessage carrying the // payload plus the MQTT 5 CorrelationData and ResponseTopic // properties (empty for MQTT 3.1.1 backends). virtual void Subscribe(const FString& Topic, FMessageCallback OnMessage, FSubscribeAckCallback OnSubscribed = nullptr) = 0; virtual void Unsubscribe(const TArray& Topics) = 0; virtual void Publish(const FString& Topic, const TArray& Payload, const FPublishOptions& Options) = 0; virtual FOnConnectionChanged& OnConnectionChanged() = 0; }; ``` * **`UApiGearMQTTClient`** — a `UAbstractApiGearConnection` consumed by generated per-interface clients. Holds the broker URL, dispatches subscribe/publish calls to the underlying `IApiGearMqttClient`, and tracks pending operation responses. * **`UApiGearMQTTHost`** — a plain `UObject` (not a subsystem) that adapters use to publish property changes, signals, and operation replies. Multiple hosts can be instantiated to serve different scopes if needed. * **`MqttLoopbackClient`** — an in-process `IApiGearMqttClient` plus broker for unit testing. Routes publishes directly to subscribers without touching the network. The `UApiGearMQTTClient` constant `ApiGearMQTTProtocolIdentifier` is `"mqtt"`. Generated clients verify this when binding to a connection from the `UApiGearConnectionsStore`. note This is the [Strategy Pattern](https://en.wikipedia.org/wiki/Strategy_pattern). The generated code calls into `IApiGearMqttClient`, and the concrete strategy is chosen at startup based on which backend module is loaded. ### ApiGearMQTTPaho (production backend)[​](#apigearmqttpaho-production-backend "Direct link to ApiGearMQTTPaho (production backend)") `ApiGearMQTTPaho` provides a production-ready `IApiGearMqttClient` implementation built on the [Eclipse Paho C asynchronous client](https://github.com/eclipse-paho/paho.mqtt.c) (`MQTTAsync_*` API): * `FPahoMqttClient` implements `IApiGearMqttClient` against `MQTTAsync` handles. * The module's `StartupModule` registers a factory under the `"mqtt"` protocol identifier: ``` void FApiGearMQTTPahoModule::StartupModule() { UApiGearConnectionsStore::RegisterConnectionFactory( ApiGearMQTTProtocolIdentifier, &CreatePahoMQTTConnection); } ``` * The factory wires a fresh `FPahoMqttClient` into a new `UApiGearMQTTClient` whenever the connection store creates an MQTT connection. The `ThirdParty/PahoMQTTLibrary` module bootstraps Paho on first build: 1. Clones `https://github.com/eclipse-paho/paho.mqtt.c.git` at tag `v1.3.14` (depth 1, \~2 MB). 2. Configures and builds the static `paho-mqtt3as` library via CMake with `PAHO_WITH_SSL=ON`. 3. Links against the OpenSSL bundled with Unreal Engine (auto-detected under `Engine/Source/ThirdParty/OpenSSL`). warning **Build prerequisites**: `git` and `cmake` must be installed and on `PATH`. The first build of any MQTT-enabled plugin downloads and compiles Paho; subsequent builds reuse the cached static library. tip TLS is supported out of the box. URLs starting with `mqtts://` or `ssl://` trigger SSL configuration in the Paho backend. For broker authentication, extend `FPahoMqttClient::Connect` or pre-configure your broker accordingly. ## MQTT Client[​](#mqtt-client "Direct link to MQTT Client") The `UIoWorldHelloMQTTClient` class implements `IIoWorldHelloInterface` (via `UAbstractIoWorldHello`) and forwards calls to a remote service over MQTT: ``` UCLASS(NotBlueprintable, BlueprintType) class IOWORLDMQTT_API UIoWorldHelloMQTTClient : public UAbstractIoWorldHello { // properties FIoWorldMessage GetLast() const override; void SetLast(const FIoWorldMessage& Last) override; // operations int32 Say(const FIoWorldMessage& Msg, EIoWorldWhen When) override; TFuture SayAsync(const FIoWorldMessage& Msg, EIoWorldWhen When) override; UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello") void UseConnection(TScriptInterface InConnection); }; ``` ### How it works[​](#how-it-works "Direct link to How it works") 1. **Subscribes to topics**: On connect, the client subscribes to property, signal, and reply topics for its interface. Subscriptions are tracked so re-connection re-subscribes automatically. 2. **Caches properties**: Property values broadcast on `prop/*` topics (retained) update the local cache and fire publisher delegates. 3. **Publishes set requests**: Setters publish to `set/*` topics; the client also caches the last sent value to suppress redundant publishes. 4. **Invokes operations with MQTT 5 correlation properties**: Each operation call generates a `FGuid`, attaches it as MQTT 5 `CorrelationData` (and the per-client reply topic as `ResponseTopic`) on the PUBLISH packet, and registers a `TPromise` keyed by that GUID. The matching reply — routed via the broker to the client's response topic with `CorrelationData` echoed — resolves the promise. 5. **Reports subscription readiness**: After all topic subscriptions are acknowledged, `_SubscriptionStatusChanged` fires with `true`. Until then, the client is connected to the broker but not yet ready to use. *The client acts as a **Remote Proxy** - it implements the same interface as the backend service but exchanges payloads via the broker. Properties are cached locally for fast reads, and operation replies are correlated by GUID.* ### Properties[​](#properties "Direct link to Properties") Property getters return the locally cached value synchronized from the broker. Setters publish a JSON-encoded payload to the `set/*` topic: ``` // Getter returns the locally cached value (last received on prop/last) FIoWorldMessage UIoWorldHelloMQTTClient::GetLast() const { return Last; } // Setter publishes to "io.world/Hello/set/last" void UIoWorldHelloMQTTClient::SetLast(const FIoWorldMessage& InLast); ``` The setter skips the publish if the new value equals either the current cached value or the last sent value, reducing broker traffic when the application sets properties frequently. note Because property notifications are published as **retained** messages, a newly-subscribing client receives the current property values immediately upon subscription - no separate handshake is required. ### Operations[​](#operations "Direct link to Operations") Operations are routed through the broker. Choose between synchronous (blocking) and asynchronous (non-blocking) variants. #### Synchronous (Blocking)[​](#synchronous-blocking "Direct link to Synchronous (Blocking)") ``` int32 UIoWorldHelloMQTTClient::Say(const FIoWorldMessage& Msg, EIoWorldWhen When); ``` caution Synchronous calls block the calling thread until the broker delivers the reply. On the game thread, this can cause frame hitches or freezes depending on broker latency. **Prefer async variants for network operations.** #### Asynchronous (Non-Blocking)[​](#asynchronous-non-blocking "Direct link to Asynchronous (Non-Blocking)") Returns a `TFuture` immediately while the operation completes: ``` TFuture UIoWorldHelloMQTTClient::SayAsync(const FIoWorldMessage& Msg, EIoWorldWhen When); ``` **Usage examples:** ``` // Chain a callback (recommended for UI updates) TFuture Future = HelloClient->SayAsync(Msg, EIoWorldWhen::IWW_Now); Future.Next([this](const int32& Result) { AsyncTask(ENamedThreads::GameThread, [this, Result]() { UpdateUI(Result); }); }); // Fire and forget HelloClient->SayAsync(Msg, EIoWorldWhen::IWW_Now); ``` Internally, `SayAsync` registers a response handler keyed by a freshly-generated `FGuid`, then publishes the args-only payload `[args...]` to `io.world/Hello/rpc/say` with two MQTT 5 properties: `CorrelationData` set to the GUID bytes and `ResponseTopic` set to `io.world/Hello/rpc/say/{clientId}/result`. The service echoes `CorrelationData` on its reply, which lands on the response topic; the client matches the GUID and resolves the promise. ### Signals[​](#signals "Direct link to Signals") Signals received from the broker are decoded and broadcast to local subscribers via the publisher: ``` // Subscribe to signals using the Publisher Hello->_GetPublisher()->OnJustSaidSignalBP.AddDynamic(this, &UMyClass::OnJustSaid); ``` Signal payloads are dispatched on the game thread via `AsyncTask(ENamedThreads::GameThread, ...)`, so subscribers do not need to marshal manually. ### Subscription Status[​](#subscription-status "Direct link to Subscription Status") The MQTT client distinguishes broker connectivity from interface readiness. A client may be connected to the broker but still mid-subscription. Use the subscription status delegate: ``` // Check if currently subscribed (all topics acknowledged) if (HelloClient->_IsSubscribed()) { // Client is ready to use } // React to subscription changes HelloClient->_SubscriptionStatusChanged.AddLambda([](bool bSubscribed) { UE_LOG(LogTemp, Log, TEXT("Subscription: %s"), bSubscribed ? TEXT("ready") : TEXT("not ready")); }); ``` ### Using the MQTT Client[​](#using-the-mqtt-client "Direct link to Using the MQTT Client") #### Setup Connection[​](#setup-connection "Direct link to Setup Connection") Create an MQTT connection with `MQTTFactory::Create`, inject a concrete `IApiGearMqttClient` (Paho in production), call `Configure` with the broker URL, and register the connection with the connections store: ``` #include "ApiGearConnectionsStore.h" #include "ApiGearMQTTClient.h" #include "PahoMqttClient.h" #include "IoWorld/Generated/MQTT/IoWorldHelloMQTTClient.h" // 1. Create the connection (UApiGearMQTTClient implements IApiGearConnection) TScriptInterface Connection = MQTTFactory::Create(GetTransientPackage(), TEXT("MyMQTTConnection")); // 2. Inject the Paho backend (use mqtts:// or ssl:// in the URL for TLS) UApiGearMQTTClient* MQTT = Cast(Connection.GetObject()); MQTT->SetMqttImplementation(MakeShared()); MQTT->Configure(TEXT("tcp://localhost:1883"), /*bAutoReconnect*/ true); // 3. Register with the engine-wide connections store so generated clients can find it UApiGearConnectionsStore* Store = GEngine->GetEngineSubsystem(); Store->AddConnection(Connection); // 4. Bind the per-interface MQTT client (GameInstance subsystem) UIoWorldHelloMQTTClient* HelloClient = GetGameInstance()->GetSubsystem(); HelloClient->UseConnection(Connection); Connection->Connect(); // 5. Use as IIoWorldHelloInterface once subscriptions are acknowledged HelloClient->_SubscriptionStatusChanged.AddLambda([HelloClient](bool bReady) { if (bReady) { TScriptInterface Hello = HelloClient; FIoWorldMessage Msg; Hello->Say(Msg, EIoWorldWhen::IWW_Now); } }); ``` note `UApiGearConnectionsStore` exposes `GetConnection`, `AddConnection`, and `RegisterConnectionFactory` — there is no combined "get-or-create" helper. Either use `MQTTFactory::Create` directly (as above) or pre-configure the connection in Project Settings and look it up via `Store->GetConnection(Identifier)`. #### Using Project Settings[​](#using-project-settings "Direct link to Using Project Settings") Configure connections in Project Settings under ApiGear: 1. Open Project Settings > Plugins > ApiGear 2. Add a new connection and pick the `mqtt` protocol from the dropdown 3. Set the broker URL (e.g., `tcp://localhost:1883` or `mqtts://broker.example.com:8883`) 4. Optionally enable auto-connect The Project Settings dropdown lists every protocol that has registered a factory. Loading `ApiGearMQTTPaho` registers the `"mqtt"` entry. The generated client picks up the configured connection automatically via the module setting `MQTTConnectionIdentifier`: ``` UIoWorldSettings* settings = GetMutableDefault(); TScriptInterface MQTTConnection = AGCM->GetConnection(settings->MQTTConnectionIdentifier); ``` ## MQTT Adapter (Service)[​](#mqtt-adapter-service "Direct link to MQTT Adapter (Service)") The `UIoWorldHelloMQTTAdapter` wraps a local `IIoWorldHelloInterface` implementation and exposes it to MQTT subscribers through a `UApiGearMQTTHost`: ``` UCLASS(BlueprintType) class IOWORLDMQTT_API UIoWorldHelloMQTTAdapter : public UGameInstanceSubsystem, public IIoWorldHelloSubscriberInterface { UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello") void setBackendService(TScriptInterface InService); UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello") void setMQTTHost(TSoftObjectPtr InHost); }; ``` ### How it works[​](#how-it-works-1 "Direct link to How it works") 1. **Wraps local implementation**: Subscribes to the backend's publisher to forward signals and property changes. 2. **Subscribes to inbound topics**: When the host connects, the adapter subscribes to its `set/*` and `rpc/*` topics. 3. **Handles set requests**: Decodes the JSON payload and forwards the value to the backend. 4. **Handles operation invocations**: Reads `CorrelationData` and `ResponseTopic` from the inbound MQTT 5 properties, decodes the args-only payload, invokes the backend, and publishes the result to the caller's `ResponseTopic` with `CorrelationData` echoed back. 5. **Broadcasts updates**: When the backend's property or signal fires, the adapter publishes to `prop/*` (retained) or `sig/*`. 6. **Replays state on reconnect**: When the host reconnects, the adapter republishes current property values so retained-message storage stays consistent. *The adapter uses the **Adapter Pattern** - it wraps any `IIoWorldHelloInterface` implementation and exposes it via the broker. Multiple heterogeneous subscribers (Unreal, Python, web, embedded) can interact with it simultaneously.* ### Properties[​](#properties-1 "Direct link to Properties") When a subscriber publishes to `set/last`, the adapter decodes the JSON and forwards the value to the backend. The backend's property change notification is then broadcast on `prop/last` with `retain=true`: ``` void UIoWorldHelloMQTTAdapter::OnLastChanged(const FIoWorldMessage& InLast) { if (!Host.IsValid() || !bIsConnected) { return; } const FString Topic = TEXT("io.world/Hello/prop/last"); Host.Get()->notifyPropertyChange(Topic, UTF8_TO_TCHAR(nlohmann::json(InLast).dump().c_str())); } ``` ### Operations[​](#operations-1 "Direct link to Operations") Remote operation calls carry their correlation in the MQTT 5 `CorrelationData` property and tell the service where to publish the reply via `ResponseTopic`. The adapter just echoes those properties back, so concurrent invocations from different clients never collide: ``` // InMsg.CorrelationData and InMsg.ResponseTopic are MQTT 5 properties // from the inbound PUBLISH packet. int32 Result = BackendService->Say(Msg, When); const nlohmann::json ResponsePayload = Result; Host.Get()->notifyInvokeResponseWithCorrelation( InMsg.ResponseTopic, UTF8_TO_TCHAR(ResponsePayload.dump().c_str()), InMsg.CorrelationData); ``` ### Signals[​](#signals-1 "Direct link to Signals") The adapter subscribes to the backend implementation's publisher. When the implementation broadcasts a signal, the adapter forwards it to `sig/*`: ``` LocalImpl->_GetPublisher()->BroadcastJustSaidSignal(Msg); ``` ### Using the MQTT Adapter[​](#using-the-mqtt-adapter "Direct link to Using the MQTT Adapter") ``` #include "ApiGearMQTTHost.h" #include "IoWorld/Generated/MQTT/IoWorldHelloMQTTAdapter.h" #include "IoWorld/Implementation/IoWorldHello.h" // Get your local implementation (GameInstance subsystem) UIoWorldHelloImplementation* LocalHello = GetGameInstance()->GetSubsystem(); // Create and start an MQTT host UApiGearMQTTHost* Host = NewObject(GetTransientPackage()); // Production: wire in the Paho backend Host->SetMqttImplementation(MakeShared()); Host->Start(TEXT("tcp://localhost:1883")); // Get the adapter (GameInstance subsystem) UIoWorldHelloMQTTAdapter* Adapter = GetGameInstance()->GetSubsystem(); Adapter->setBackendService(LocalHello); Adapter->setMQTTHost(Host); // Adapter publishes property/signal changes; subscribers consume them via the broker ``` note The adapter is a `UGameInstanceSubsystem`, but the host is a plain `UObject`. This lets you instantiate multiple hosts (for example, to publish to different brokers or topic prefixes) while keeping a single adapter per interface. ## Communication Flow[​](#communication-flow "Direct link to Communication Flow") The following diagram traces a complete interaction between a client and an adapter through the broker: ## Testing with MQTT[​](#testing-with-mqtt "Direct link to Testing with MQTT") The `mqtt_tests` feature generates test fixtures that exercise the full client/adapter pair without needing a real broker: ``` 📂IoWorld/Source/IoWorldMQTT/Private/Tests ┣ 📜IoWorldHelloMQTT.spec.cpp ┣ 📜IoWorldHelloMQTTFixture.h ┗ 📜IoWorldHelloMQTTFixture.cpp ``` The fixture wires up an in-process broker (`FMqttLoopbackBroker`) and two `FMqttLoopbackClient` instances - one for the host and one for the client - so the test runs entirely in memory: ``` // from IoWorldHelloMQTTFixture.cpp auto Broker = MakeShared(); auto ClientLoopback = MakeShared(Broker, TEXT("loopback-client")); auto HostLoopback = MakeShared(Broker, TEXT("loopback-host")); Host = NewObject(GetTransientPackage()); Host->SetMqttImplementation(HostLoopback); GetAdapter()->setBackendService(service); GetAdapter()->setMQTTHost(Host.Get()); Host->Start(TEXT("loopback://test")); Connection = MQTTFactory::Create(GetTransientPackage(), TEXT("MQTTTestingConnection")); auto* MQTTConnection = Cast(Connection.GetObject()); MQTTConnection->SetMqttImplementation(ClientLoopback); MQTTConnection->Configure(TEXT("loopback://test"), false); ``` These tests verify: * Subscription handshake and `_IsSubscribed()` readiness signaling * Property synchronization via retained messages * Operation invocation and per-client reply routing * Signal propagation onto the game thread ### Running Tests[​](#running-tests "Direct link to Running Tests") ``` UnrealEditor-Cmd.exe YourProject.uproject -ExecCmds="Automation RunTests IoWorld.Hello.MQTT" ``` tip The loopback transport is also useful in your own tests when you need deterministic MQTT semantics without provisioning a broker. Construct an `FMqttLoopbackBroker`, share it across as many `FMqttLoopbackClient`s as you need, and inject them via `SetMqttImplementation` on the host and connection. ## Best Practices[​](#best-practices "Direct link to Best Practices") ### Broker Selection[​](#broker-selection "Direct link to Broker Selection") * For local development, [Mosquitto](https://mosquitto.org/) is the simplest broker to run. * For production, choose a broker that supports your scale and reliability requirements (EMQX, HiveMQ, AWS IoT Core, Azure IoT Hub). * Configure broker authentication, ACLs, and TLS appropriately - the generated code defers credential handling to your `IApiGearMqttClient` implementation. ### Topic Discipline[​](#topic-discipline "Direct link to Topic Discipline") * The generated topic structure (`{module}/{Interface}/{action}/{member}`) is fixed by the template. Do not publish to these topics from non-generated code unless you fully understand the payload format. * If multiple Unreal instances run the same plugin, they all subscribe to and publish on the same topics. Coordinate which instance acts as the adapter. ### Connection Lifecycle[​](#connection-lifecycle "Direct link to Connection Lifecycle") * Wait for `_SubscriptionStatusChanged(true)` before invoking operations - publishing before subscriptions are acknowledged risks losing replies. * Re-subscription on reconnect is automatic. Pending operation promises are cancelled on `Deinitialize` to avoid use-after-free in test teardown. * Adapters re-publish their current property values when the host reconnects, keeping retained-message storage consistent. ### Performance Considerations[​](#performance-considerations "Direct link to Performance Considerations") * MQTT publishes are network calls - avoid invoking operations every frame. * Property setters automatically suppress duplicate publishes; rely on this rather than guarding manually. * Use QoS 1 (the default) unless you have a specific reason to relax delivery guarantees. * The generated MQTT code includes CPU profiler instrumentation (`TRACE_CPUPROFILER_EVENT_SCOPE_STR`) for performance tracing in Unreal Insights. --- # Message Bus The `msgbus` feature provides client and adapter implementations using Unreal Engine's built-in [Message Bus](https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Messaging/IMessageBus) system. This enables: * **Inter-process communication**: Connect Unreal applications running on the same machine * **Editor-to-game communication**: Share data between editor tools and PIE sessions * **Distributed Unreal systems**: Connect multiple Unreal instances in a local network Unlike [OLink](/template-unreal/docs/features/olink.md), which uses WebSockets, Message Bus uses Unreal's native UDP-based messaging system optimized for Unreal-to-Unreal communication. ## When to use Message Bus vs OLink[​](#when-to-use-message-bus-vs-olink "Direct link to When to use Message Bus vs OLink") | Feature | Message Bus | OLink | | ------------------ | ------------------------------- | -------------------------- | | Protocol | Unreal's UDP Message Bus | WebSocket | | Best for | Unreal-to-Unreal communication | Cross-platform, simulation | | Setup | Zero configuration on localhost | Requires URL configuration | | Network | Local network / same machine | Any network | | ApiGear Simulation | No | Yes | | Non-Unreal clients | No | Yes | tip Use **Message Bus** when communicating between Unreal applications. Use **OLink** when connecting to non-Unreal services or ApiGear simulation tools. ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With our example API definition: Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` The following file structure is generated in the `IoWorldMsgBus` module: ``` 📂IoWorld/Source/IoWorldMsgBus ┣ 📂Private ┃ ┣ 📂Generated ┃ ┃ ┣ 📜IoWorldMsgBus.cpp ┃ ┃ ┗ 📂MsgBus ┃ ┃ ┣ 📜IoWorldHelloMsgBusClient.cpp ┃ ┃ ┗ 📜IoWorldHelloMsgBusAdapter.cpp ┃ ┗ 📂Tests ┃ ┣ 📜IoWorldHelloMsgBus.spec.cpp ┃ ┣ 📜IoWorldHelloMsgBusFixture.h ┃ ┗ 📜IoWorldHelloMsgBusFixture.cpp ┣ 📂Public ┃ ┗ 📂IoWorld ┃ ┣ 📜IoWorldMsgBus.h ┃ ┗ 📂Generated/MsgBus ┃ ┣ 📜IoWorldHelloMsgBusClient.h ┃ ┣ 📜IoWorldHelloMsgBusAdapter.h ┃ ┗ 📜IoWorldHelloMsgBusMessages.h ┗ 📜IoWorldMsgBus.Build.cs ``` ## Message Bus Client[​](#message-bus-client "Direct link to Message Bus Client") The `UIoWorldHelloMsgBusClient` class implements `IIoWorldHelloInterface` and communicates with a remote adapter via Message Bus. ### How it works[​](#how-it-works "Direct link to How it works") 1. **Discovers service**: Publishes a discovery message to locate the adapter on the network 2. **Subscribes to messages**: Listens for property changes, signals, and operation replies from the adapter 3. **Publishes requests**: Sends property change requests and operation calls 4. **Caches state**: Maintains local copies of properties synchronized from the adapter 5. **Monitors health**: Tracks connection status with heartbeat ping/pong *The client acts as a **Remote Proxy** - it implements the same interface as the backend service but forwards calls over Message Bus. Properties are cached locally for fast reads.* ### Connection Management[​](#connection-management "Direct link to Connection Management") The client provides methods to manage the connection lifecycle: ``` UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello|Remote") void _Connect(); UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello|Remote") void _Disconnect(); UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello|Remote") bool _IsConnected() const; ``` The client automatically discovers the adapter service using Unreal's Message Bus. The endpoint is registered with the address `"ApiGear/IoWorld/Hello/Client"`. ### Connection Status[​](#connection-status "Direct link to Connection Status") The client broadcasts connection status changes through delegates: ``` UPROPERTY(BlueprintAssignable, Category = "ApiGear|IoWorld|Hello|Remote", DisplayName = "Connection Status Changed") FIoWorldHelloConnectionStatusChangedDelegateBP _ConnectionStatusChangedBP; FIoWorldHelloConnectionStatusChangedDelegate _ConnectionStatusChanged; ``` ### Statistics and Health Monitoring[​](#statistics-and-health-monitoring "Direct link to Statistics and Health Monitoring") The client tracks round-trip time statistics (current, average, min, max RTT in milliseconds) through `FIoWorldHelloStats`. Access statistics and subscribe to updates: ``` const FIoWorldHelloStats& Stats = HelloClient->_GetStats(); HelloClient->_StatsUpdated.AddDynamic(this, &UMyClass::OnStatsUpdated); ``` ### API Implementation[​](#api-implementation "Direct link to API Implementation") The client implements the standard `IIoWorldHelloInterface` (see [API documentation](/template-unreal/docs/features/api.md)): * **Properties**: Getters return locally cached values synchronized from the adapter. Setters send change requests to the adapter. The implementation optimizes network load by only sending when values actually change. * **Operations**: Calls are sent to the adapter and block until a response is received. Returns a default value if not connected. * **Signals**: Subscribe to signals through the Publisher, which broadcasts events received from the adapter. ### Using the Client[​](#using-the-client "Direct link to Using the Client") ``` #include "IoWorld/Generated/MsgBus/IoWorldHelloMsgBusClient.h" UIoWorldHelloMsgBusClient* HelloClient = GetGameInstance()->GetSubsystem(); HelloClient->_Connect(); HelloClient->_ConnectionStatusChangedBP.AddDynamic(this, &UMyClass::OnConnectionStatusChanged); TScriptInterface Hello = HelloClient; Hello->Say(Msg, EIoWorldWhen::IWW_Now); Hello->_GetPublisher()->OnLastChangedBP.AddDynamic(this, &UMyClass::OnLastChanged); ``` ## Message Bus Adapter[​](#message-bus-adapter "Direct link to Message Bus Adapter") The `UIoWorldHelloMsgBusAdapter` wraps a local `IIoWorldHelloInterface` implementation and exposes it via Message Bus. ### How it works[​](#how-it-works-1 "Direct link to How it works") 1. **Wraps local implementation**: Uses an existing implementation (e.g., your stub) 2. **Handles discovery**: Responds to client discovery messages and initializes connections 3. **Handles requests**: Processes property change requests and operation calls from clients 4. **Broadcasts updates**: Publishes property changes and signals to all subscribed clients 5. **Tracks clients**: Monitors connected clients and handles timeouts *The adapter uses the **Adapter Pattern** - it wraps any `IIoWorldHelloInterface` implementation and exposes it over Message Bus. Multiple clients can connect simultaneously.* ### Connection Management[​](#connection-management-1 "Direct link to Connection Management") The adapter provides methods to control when it accepts client connections: ``` UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello|Remote") void _StartListening(); UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello|Remote") void _StopListening(); UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello|Remote") bool _IsListening() const; ``` ### Client Tracking[​](#client-tracking "Direct link to Client Tracking") The adapter provides delegates to monitor client connections: ``` UPROPERTY(BlueprintAssignable, Category = "ApiGear|IoWorld|Hello|Remote", DisplayName = "New client connected") FIoWorldHelloClientConnectedDelegate _OnClientConnected; UPROPERTY(BlueprintAssignable, Category = "ApiGear|IoWorld|Hello|Remote", DisplayName = "Client disconnected") FIoWorldHelloClientDisconnectedDelegate _OnClientDisconnected; UPROPERTY(BlueprintAssignable, Category = "ApiGear|IoWorld|Hello|Remote", DisplayName = "Client timed out") FIoWorldHelloClientTimeoutDelegate _OnClientTimeout; UPROPERTY(BlueprintAssignable, Category = "ApiGear|IoWorld|Hello|Remote", DisplayName = "Clients connected count changed") FIoWorldHelloClientCountDelegate _OnClientsConnectedCountChanged; UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello|Remote") const int32 _GetClientsConnectedCount() const; ``` ### Backend Service[​](#backend-service "Direct link to Backend Service") The adapter wraps an existing implementation that handles the actual business logic: ``` UFUNCTION(BlueprintCallable, Category = "ApiGear|IoWorld|Hello") void _setBackendService(TScriptInterface InService); ``` ### Using the Adapter[​](#using-the-adapter "Direct link to Using the Adapter") ``` #include "IoWorld/Generated/MsgBus/IoWorldHelloMsgBusAdapter.h" #include "IoWorld/Implementation/IoWorldHello.h" UIoWorldHelloImplementation* LocalHello = GetGameInstance()->GetSubsystem(); UIoWorldHelloMsgBusAdapter* Adapter = GetGameInstance()->GetSubsystem(); Adapter->_setBackendService(LocalHello); Adapter->_StartListening(); Adapter->_OnClientConnected.AddDynamic(this, &UMyClass::OnClientConnected); Adapter->_OnClientDisconnected.AddDynamic(this, &UMyClass::OnClientDisconnected); ``` ## Testing with Message Bus[​](#testing-with-message-bus "Direct link to Testing with Message Bus") The `msgbus_tests` feature generates test fixtures: ``` IoWorld/Source/IoWorldMsgBus/Private/Tests ├── IoWorldHelloMsgBus.spec.cpp ├── IoWorldHelloMsgBusFixture.h └── IoWorldHelloMsgBusFixture.cpp ``` ### Running Tests[​](#running-tests "Direct link to Running Tests") ``` UnrealEditor-Cmd.exe YourProject.uproject -ExecCmds="Automation RunTests IoWorld.MsgBus" ``` ## Best Practices[​](#best-practices "Direct link to Best Practices") ### Lifecycle Management[​](#lifecycle-management "Direct link to Lifecycle Management") * Initialize adapters before clients start connecting * Clean up adapters when no longer needed to stop message handling * Clients should handle adapter unavailability gracefully * Monitor connection status changes to react to disconnections ### Performance[​](#performance "Direct link to Performance") * Message Bus is efficient for moderate message rates * For high-frequency updates, consider using direct connections * Property changes are only broadcast when values actually change * The client tracks sent values to avoid redundant network requests ## Message Protocol[​](#message-protocol "Direct link to Message Protocol") The Message Bus feature uses generated message structures (`IoWorldHelloMsgBusMessages.h`) for communication between client and adapter. These messages handle: * **Connection lifecycle** - service discovery, initialization, and disconnect * **Heartbeat monitoring** - ping/pong messages to detect connection health * **Property synchronization** - change requests and notifications * **Operation request/response** - calls with correlation IDs for matching replies * **Signal broadcasting** - events sent to all connected clients note You don't need to interact with these message types directly - they are used internally by the client and adapter classes. ### Communication Flow[​](#communication-flow "Direct link to Communication Flow") The following diagram shows how messages flow between client and adapter for each type of interaction: --- # OLink The `olink` feature provides client and server adapters for the [ObjectLink](/docs/protocols/objectlink/intro.md) protocol. This enables: * **Remote connections**: Connect your Unreal application to services running in other processes or machines * **Cross-platform communication**: Communicate with applications built using other ApiGear templates (Qt, C++, etc.) * **Simulation**: Connect to the ApiGear simulation tools for testing without a real backend Use an *OLink client* instead of your local implementation to connect to a remote service. Use an *OLink adapter* (server) to expose your local implementation to remote clients. note The OLink client and adapter are ready-to-use. They handle all network communication transparently, so you can use them just like local implementations. ## ApiGear ObjectLink Protocol[​](#apigear-objectlink-protocol "Direct link to ApiGear ObjectLink Protocol") The [ObjectLink](/docs/protocols/objectlink/intro.md) protocol is a lightweight WebSocket-based protocol for object communication. It supports: * Remote property changes (client requests, server notifies) * Signal emission notifications * Remote method invocation with responses The generated code uses the ObjectLink protocol library included in the `ApiGear` plugin. ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With our example API definition: Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` The following file structure is generated: ``` 📂ApiGear/Source ┣ 📂ApiGearOLink ┃ ┣ 📂Private ┃ ┃ ┣ 📜OLinkClientConnection.cpp ┃ ┃ ┣ 📜OLinkHost.cpp ┃ ┃ ┣ 📜OLinkHostConnection.cpp ┃ ┃ ┗ 📜OLinkSink.cpp ┃ ┣ 📂Public ┃ ┃ ┣ 📜OLinkClientConnection.h ┃ ┃ ┣ 📜OLinkHost.h ┃ ┃ ┗ 📜OLinkSink.h ┃ ┗ 📜apigearolink.Build.cs ┗ 📂ThirdParty/OLinkProtocolLibrary 📂IoWorld/Source/IoWorldOLink ┣ 📂Private ┃ ┗ 📂Generated ┃ ┣ 📜IoWorldOLink.cpp ┃ ┗ 📂OLink ┃ ┣ 📜IoWorldHelloOLinkClient.cpp ┃ ┣ 📜IoWorldHelloOLinkAdapter.cpp ┃ ┣ 📜IoWorldHelloOLinkSource.h ┃ ┗ 📜IoWorldHelloOLinkSource.cpp ┣ 📂Public ┃ ┗ 📂IoWorld ┃ ┣ 📜IoWorldOLink.h ┃ ┗ 📂Generated/OLink ┃ ┣ 📜IoWorldHelloOLinkClient.h ┃ ┗ 📜IoWorldHelloOLinkAdapter.h ┗ 📜IoWorldOLink.Build.cs ``` ## ApiGear OLink Network Layer[​](#apigear-olink-network-layer "Direct link to ApiGear OLink Network Layer") The `ApiGearOLink` module provides the network infrastructure: ### OLinkClientConnection[​](#olinkclientconnection "Direct link to OLinkClientConnection") Manages WebSocket connections for OLink clients: * Connects to a remote OLink host * Routes incoming messages to registered client sinks * Handles reconnection logic * Manages message queuing during disconnection ### OLinkHost[​](#olinkhost "Direct link to OLinkHost") Hosts OLink services for remote clients: * Listens for incoming WebSocket connections * Routes client requests to registered service adapters * Manages multiple simultaneous client connections ## OLink Client[​](#olink-client "Direct link to OLink Client") The `UIoWorldHelloOLinkClient` class implements `IIoWorldHelloInterface` and connects to a remote OLink service. ### How it works[​](#how-it-works "Direct link to How it works") 1. **Properties**: Getter returns the locally cached value (synchronized from server). Setter sends a change request to the server. 2. **Operations**: Calls are forwarded to the remote server. The client waits for the response. 3. **Signals**: When the server emits a signal, the client receives it and broadcasts locally. *The client acts as a **Remote Proxy** - it implements the same interface as the backend service but forwards calls over WebSocket. Properties are cached locally for fast reads.* ### Properties[​](#properties "Direct link to Properties") Properties are synchronized between client and server: ``` // Getter returns the locally cached value (synchronized from server) FIoWorldMessage UIoWorldHelloOLinkClient::GetLast() const { return Last; } // Setter sends a change request to the server void UIoWorldHelloOLinkClient::SetLast(const FIoWorldMessage& InLast); ``` The setter validates the connection and optimizes network traffic by skipping redundant requests when the value hasn't changed. note When a client successfully links to a server, it receives the current property values automatically. ### Operations[​](#operations "Direct link to Operations") Operations are forwarded to the remote server. Choose between synchronous (blocking) and asynchronous (non-blocking) variants. #### Synchronous (Blocking)[​](#synchronous-blocking "Direct link to Synchronous (Blocking)") ``` int32 UIoWorldHelloOLinkClient::Say(const FIoWorldMessage& Msg, EIoWorldWhen When); ``` caution Synchronous calls block the calling thread. On the game thread, this can cause frame hitches or freezes depending on network latency. **Prefer async variants for network operations.** #### Asynchronous (Non-Blocking)[​](#asynchronous-non-blocking "Direct link to Asynchronous (Non-Blocking)") Returns a `TFuture` immediately while the operation executes: ``` TFuture UIoWorldHelloOLinkClient::SayAsync(const FIoWorldMessage& Msg, EIoWorldWhen When); ``` **Usage examples:** ``` // Chain a callback (recommended for UI updates) TFuture Future = HelloClient->SayAsync(Msg, EIoWorldWhen::IWW_Now); Future.Next([this](const int32& Result) { AsyncTask(ENamedThreads::GameThread, [this, Result]() { UpdateUI(Result); }); }); // Fire and forget HelloClient->SayAsync(Msg, EIoWorldWhen::IWW_Now); ``` #### Blueprint Async Operations[​](#blueprint-async-operations "Direct link to Blueprint Async Operations") In Blueprints, use the async version which appears as a latent node with execution pins. The latent action handles waiting internally without blocking the game thread. ### Signals[​](#signals "Direct link to Signals") Signals received from the server are broadcast to local subscribers via the Publisher: ``` // Subscribe to signals using the Publisher Hello->_GetPublisher()->OnJustSaidSignalBP.AddDynamic(this, &UMyClass::OnJustSaid); ``` ### Using the OLink Client[​](#using-the-olink-client "Direct link to Using the OLink Client") #### Setup Connection[​](#setup-connection "Direct link to Setup Connection") In your game code or a custom subsystem: ``` #include "OLinkClientConnection.h" #include "IoWorld/Generated/OLink/IoWorldHelloOLinkClient.h" // Get or create the connection UOLinkClientConnection* Connection = NewObject(); Connection->Configure(TEXT("ws://localhost:8182/ws"), false); Connection->Connect(); // Get the OLink client (GameInstance subsystem) UIoWorldHelloOLinkClient* HelloClient = GetGameInstance()->GetSubsystem(); HelloClient->UseConnection(Connection); // Associate with the connection // Use as IIoWorldHelloInterface TScriptInterface Hello = HelloClient; Hello->Say(Msg, EIoWorldWhen::IWW_Now); ``` #### Using Project Settings[​](#using-project-settings "Direct link to Using Project Settings") Configure connections in Project Settings under ApiGear: 1. Open Project Settings > Plugins > ApiGear 2. Add a new OLink connection 3. Set the server URL (e.g., `ws://localhost:8182/ws`) 4. Enable auto-connect if desired Then access the configured connection: ``` #include "ApiGearConnectionsStore.h" UApiGearConnectionsStore* Store = GetGameInstance()->GetSubsystem(); UOLinkClientConnection* Connection = Store->GetOLinkConnection(TEXT("MyConnection")); ``` #### Blueprint Setup[​](#blueprint-setup "Direct link to Blueprint Setup") 1. Get the `ApiGearConnectionsStore` subsystem 2. Call `GetOLinkConnection` with your connection name 3. Create your OLink client instance 4. Use it like any other interface implementation #### Subscription Status[​](#subscription-status "Direct link to Subscription Status") The OLink client provides delegates to track when it successfully links to a server: ``` // Check if currently subscribed (linked to server) if (HelloClient->_IsSubscribed()) { // Client is ready to use } // React to subscription changes HelloClient->_SubscriptionStatusChanged.AddLambda([](bool bSubscribed) { UE_LOG(LogTemp, Log, TEXT("Subscription: %s"), bSubscribed ? TEXT("linked") : TEXT("unlinked")); }); ``` ## OLink Adapter (Server)[​](#olink-adapter-server "Direct link to OLink Adapter (Server)") The `UIoWorldHelloOLinkAdapter` wraps a local `IIoWorldHelloInterface` implementation and exposes it to remote clients. warning The OLink adapter is disabled on mobile platforms (iOS, Android, QNX). Mobile devices typically act as clients only, not servers. ### How it works[​](#how-it-works-1 "Direct link to How it works") 1. **Wraps local implementation**: Takes an existing implementation (e.g., your stub) 2. **Handles remote requests**: Forwards property changes and operation calls to the wrapped implementation 3. **Broadcasts updates**: Sends property changes and signals to all connected clients *The adapter uses the **Adapter Pattern** - it wraps any `IIoWorldHelloInterface` implementation and exposes it over WebSocket. Multiple clients can connect simultaneously.* ### Properties[​](#properties-1 "Direct link to Properties") When a client requests a property change, the adapter receives the request, calls the wrapped implementation, and the property change is broadcast to all connected clients. ### Operations[​](#operations-1 "Direct link to Operations") Remote operation calls are forwarded to the local implementation. The result is sent back to the calling client. ### Signals[​](#signals-1 "Direct link to Signals") The adapter subscribes to the local implementation's signals. When the implementation emits a signal via the Publisher, the adapter broadcasts it to all connected remote clients: ``` LocalImpl->_GetPublisher()->BroadcastJustSaidSignal(Msg); ``` ### Using the OLink Adapter[​](#using-the-olink-adapter "Direct link to Using the OLink Adapter") ``` #include "OLinkHost.h" #include "IoWorld/Generated/OLink/IoWorldHelloOLinkAdapter.h" #include "IoWorld/Implementation/IoWorldHello.h" // Get your local implementation (GameInstance subsystem) UIoWorldHelloImplementation* LocalHello = GetGameInstance()->GetSubsystem(); // Get the OLink host (GameInstance subsystem) UOLinkHost* Host = GetGameInstance()->GetSubsystem(); Host->Start(8182); // Listen on port 8182 // Get the adapter (GameInstance subsystem) UIoWorldHelloOLinkAdapter* Adapter = GetGameInstance()->GetSubsystem(); Adapter->setBackendService(LocalHello); // Note: lowercase 's' Adapter->setOLinkHost(Host); // Register with the host // Now remote clients can connect and use your implementation ``` ## Testing with OLink[​](#testing-with-olink "Direct link to Testing with OLink") The `olink_tests` feature generates test fixtures for OLink functionality: ``` 📂IoWorld/Source/IoWorldOLink/Private/Tests ┣ 📜IoWorldHelloOLink.spec.cpp ┣ 📜IoWorldHelloOLinkFixture.h ┗ 📜IoWorldHelloOLinkFixture.cpp ``` These tests verify: * Client-server connection * Property synchronization * Operation invocation and response * Signal propagation ## Best Practices[​](#best-practices "Direct link to Best Practices") ### Connection Management[​](#connection-management "Direct link to Connection Management") * Use the `ApiGearConnectionsStore` for centralized connection management * Configure connections in Project Settings for easy adjustment * Handle connection/disconnection events for UI feedback ### Error Handling[​](#error-handling "Direct link to Error Handling") ``` Connection->GetOnConnectionStatusChangedDelegate().AddDynamic(this, &UMyClass::OnConnectionChanged); void UMyClass::OnConnectionChanged(bool bConnected) { if (!bConnected) { // Handle disconnection UE_LOG(LogTemp, Warning, TEXT("OLink connection lost")); } } ``` ### Performance Considerations[​](#performance-considerations "Direct link to Performance Considerations") * OLink operations are network calls - avoid calling them every frame * Use property change delegates for reactive updates instead of polling * Consider async operation variants for non-blocking behavior The generated OLink code includes CPU profiler instrumentation (`TRACE_CPUPROFILER_EVENT_SCOPE_STR`) for performance tracing in Unreal Insights. --- # Stubs The `stubs` feature generates: * Fully instantiable implementation classes as GameInstance Subsystems * A starting point for your business logic * Test fixtures for unit testing your implementations The stub implementations inherit from the abstract base classes generated by the [api](/template-unreal/docs/features/api.md) feature and provide complete, working implementations that you can customize. tip Stub files are marked with `preserve: true` in the template rules. This means they won't be overwritten when you regenerate code, so your custom logic is safe. note For network connectivity, you can use [OLink](/template-unreal/docs/features/olink.md) clients instead of stubs. Both implement the same interface, so you can swap implementations without changing your application code. ## File overview for module[​](#file-overview-for-module "Direct link to File overview for module") With our example API definition: Hello World API (click to expand) ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` The following file structure is generated in the `IoWorldImplementation` module: ``` 📂IoWorld/Source/IoWorldImplementation ┣ 📂Private ┃ ┣ 📂Implementation ┃ ┃ ┗ 📜IoWorldHello.cpp ┃ ┣ 📂Tests ┃ ┃ ┣ 📜IoWorldHelloImpl.spec.cpp ┃ ┃ ┣ 📜IoWorldHelloImplFixture.h ┃ ┃ ┗ 📜IoWorldHelloImplFixture.cpp ┃ ┗ 📂Generated ┃ ┗ 📜IoWorldImplementation.cpp ┣ 📂Public ┃ ┗ 📂IoWorld ┃ ┣ 📂Implementation ┃ ┃ ┗ 📜IoWorldHello.h ┃ ┗ 📜IoWorldImplementation.h ┗ 📜IoWorldImplementation.Build.cs ``` ## Implementation[​](#implementation "Direct link to Implementation") Files `IoWorldHello.h` and `IoWorldHello.cpp` contain your implementation of the `IIoWorldHelloInterface`. The implementation class inherits from `UAbstractIoWorldHello` (see [api](/template-unreal/docs/features/api.md)) which provides property storage, Publisher for notifications, and subsystem lifecycle. ### GameInstance Subsystem[​](#gameinstance-subsystem "Direct link to GameInstance Subsystem") The implementation is a `UGameInstanceSubsystem`, which means: * **Automatic lifecycle**: Created when the GameInstance initializes, destroyed when it shuts down * **Singleton access**: Only one instance exists per GameInstance * **Easy access**: Available from any Actor, Component, or other object via `GetGameInstance()->GetSubsystem()` * **Blueprint-friendly**: Accessible through "Get Game Instance Subsystem" node ### Properties[​](#properties "Direct link to Properties") Property getters and setters are pre-implemented. The setter automatically: 1. Checks if the value actually changed (avoiding unnecessary notifications) 2. Updates the stored value (inherited from abstract class) 3. Broadcasts the change via the Publisher to all subscribers You typically don't need to modify the property accessors unless you need custom validation or side effects. Use `_ResetProperties()` to restore all properties to their default values (with change broadcasts). ### Operations[​](#operations "Direct link to Operations") Operations are generated with empty implementations for you to fill in: ``` int32 UIoWorldHelloImplementation::Say(const FIoWorldMessage& Msg, EIoWorldWhen When) { // Add your business logic here return ProcessMessage(Msg, When); } ``` The generated code includes CPU profiler instrumentation (`TRACE_CPUPROFILER_EVENT_SCOPE_STR`) for performance tracing in Unreal Insights. #### Async Operations[​](#async-operations "Direct link to Async Operations") The abstract base class provides async wrappers that automatically call your synchronous implementation on a thread pool. You don't need to implement async versions yourself: ``` // Your implementation only needs the synchronous method int32 UIoWorldHelloImplementation::Say(const FIoWorldMessage& Msg, EIoWorldWhen When) { // Your business logic here return ProcessMessage(Msg); } // Callers can use async variants - they automatically call Say() on a thread pool TFuture Future = Hello->SayAsync(Msg, EIoWorldWhen::IWW_Now); ``` If you need custom async behavior (e.g., for operations that are naturally asynchronous), you can override the async methods: ``` // Override for custom async behavior TFuture UIoWorldHelloImplementation::SayAsync(const FIoWorldMessage& Msg, EIoWorldWhen When) { // Custom async implementation TPromise Promise; TFuture Future = Promise.GetFuture(); // Start async work AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [this, Msg, When, Promise = MoveTemp(Promise)]() mutable { int32 Result = DoAsyncWork(Msg, When); Promise.SetValue(Result); }); return Future; } ``` tip When implementing operations, remember to: * Emit signals when appropriate using the Publisher's broadcast methods * Handle errors gracefully * The default async implementation runs on a thread pool - ensure thread safety if needed ### Signals[​](#signals "Direct link to Signals") To emit a signal from your implementation, use the Publisher: ``` void UIoWorldHelloImplementation::SomeInternalMethod() { FIoWorldMessage Msg; Msg.content = TEXT("Something happened"); // Emit the signal via Publisher _GetPublisher()->BroadcastJustSaidSignal(Msg); } ``` ## Accessing the Implementation[​](#accessing-the-implementation "Direct link to Accessing the Implementation") ### Module Dependency[​](#module-dependency "Direct link to Module Dependency") Add the Implementation module to your `.Build.cs`: ``` PublicDependencyModuleNames.AddRange(new string[] { "IoWorldImplementation" }); ``` ### From Blueprints[​](#from-blueprints "Direct link to From Blueprints") 1. Use the "Get Game Instance Subsystem" node 2. Select `UIoWorldHelloImplementation` as the class 3. Use the returned reference to call operations or bind to events ### From C++[​](#from-c "Direct link to From C++") ``` #include "IoWorld/Implementation/IoWorldHello.h" // From any UObject with access to the world UIoWorldHelloImplementation* Hello = GetGameInstance()->GetSubsystem(); if (Hello) { FIoWorldMessage Msg; Msg.content = TEXT("Hello"); Hello->Say(Msg, EIoWorldWhen::IWW_Now); } ``` Using `TScriptInterface` for interface-based access: ``` #include "IoWorld/Generated/api/IoWorldHelloInterface.h" #include "IoWorld/Implementation/IoWorldHello.h" TScriptInterface Hello = GetGameInstance()->GetSubsystem(); // Now use through the interface Hello->SetLast(Msg); ``` ## Tests[​](#tests "Direct link to Tests") The stubs feature generates test fixtures for each interface. ### Test Files[​](#test-files "Direct link to Test Files") * `IoWorldHelloImpl.spec.cpp` - Test specifications using Unreal's automation testing framework * `IoWorldHelloImplFixture.h/cpp` - Test fixture with setup and helper methods The generated tests cover: * Property default values * Property change notifications (both C++ and Blueprint delegates) * Operations (sync and async) * Signals (both C++ and Blueprint delegates) ### Running Tests[​](#running-tests "Direct link to Running Tests") Run the generated tests using Unreal's test framework: 1. **From Editor**: Window > Developer Tools > Session Frontend > Automation 2. **From Command Line**: ``` UnrealEditor-Cmd.exe YourProject.uproject -ExecCmds="Automation RunTests IoWorld" ``` ### Adding Your Own Tests[​](#adding-your-own-tests "Direct link to Adding Your Own Tests") The fixture provides access to the implementation through `GetImplementation()`. Add tests to the spec file: ``` It("MyCustomTest", [this]() { FIoWorldMessage Msg; Msg.content = TEXT("Test"); int32 Result = ImplFixture->GetImplementation()->Say(Msg, EIoWorldWhen::IWW_Now); TestEqual(TEXT("Expected result"), Result, ExpectedValue); }); ``` ## Best Practices[​](#best-practices "Direct link to Best Practices") ### Thread Safety[​](#thread-safety "Direct link to Thread Safety") The generated stubs are not thread-safe by default. If you need thread-safe access, use Unreal's `FCriticalSection` for property access and ensure Publisher broadcasts happen on the game thread via `AsyncTask(ENamedThreads::GameThread, ...)`. ### Initialization[​](#initialization "Direct link to Initialization") Override the `Initialize` method from the abstract base class for setup: ``` void UIoWorldHelloImplementation::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); // Initialize default values Last.content = TEXT("Default"); // Set up any required connections or resources } ``` ### Cleanup[​](#cleanup "Direct link to Cleanup") Override `Deinitialize` to clean up resources: ``` void UIoWorldHelloImplementation::Deinitialize() { // Clean up connections, timers, etc. Super::Deinitialize(); } ``` --- # Template Unreal Engine This is the documentation for the *Unreal Engine* template for the [ApiGear](/docs/guide/quick-start.md) code generator. It generates Unreal Engine plugins from your API definitions, with full support for Blueprints and C++. The documentation is split into the following sections: * [Quick-Start](/template-unreal/docs/quickstart.md?current-template=template-unreal) is the easiest way to get started * [Features](/template-unreal/docs/features.md) explains the available code generator features, what code is generated, and how to use it * [Licensing & Author Info](/template-unreal/docs/features/licensing.md) covers license files, SPDX headers, and customizing author metadata in generated plugins --- # Quick-Start The Quick-Start guide explains how to, in few steps, get from an API to a functional *Unreal Engine* plugin. ## 1. Install the Code Generator[​](#1-install-the-code-generator "Direct link to 1. Install the Code Generator") ApiGear provides two powerful tools: a Studio and a Command Line Interface (CLI). The Studio is a graphical tool for creating, editing API modules and projects, while the CLI is a command-line tool for generating code from API modules. Both offer comprehensive functionality, including SDK template management, API project creation, code generation, API monitoring, and API simulation. [Download Studio](https://github.com/apigear-io/studio/releases/latest) [Download CLI](https://github.com/apigear-io/cli/releases/latest) ### Install ApiGear Studio[​](#install-apigear-studio "Direct link to Install ApiGear Studio") ApiGear Studio is available for macOS, Windows, and Linux. You can download it from the [ApiGear Studio GitHub releases page](https://github.com/apigear-io/studio/releases/latest). For more detailed information on ApiGear Studio, refer to the dedicated [studio documentation](/docs/studio/intro.md). ### Install ApiGear CLI[​](#install-apigear-cli "Direct link to Install ApiGear CLI") The ApiGear CLI is a versatile command-line tool for generating code from API modules. It's available for macOS, Windows, and Linux. Download the latest version from the [ApiGear CLI GitHub releases page](https://github.com/apigear-io/cli/releases/latest). The CLI offers all the features available in the Studio. For more detailed information on the ApiGear CLI, consult the dedicated [CLI documentation](/docs/cli/intro.md). ## 2. Obtain the Template[​](#2-obtain-the-template "Direct link to 2. Obtain the Template") There are several methods to obtain the template: installation via the Studio, installation via the CLI, or cloning/downloading from GitHub. This step is optional The ApiGear Studio and the CLI detect the specified template in the [solution document](#solution-file) and install it automatically. Optional steps (click to expand) ### Installation via CLI[​](#installation-via-cli "Direct link to Installation via CLI") When using the CLI, only the highlighted line needs to be executed. You can verify the successful installation using the `template cache` command. * Unreal Engine * C++14 * Qt6 * Python ``` $ apigear template install apigear-io/template-unreal@v3.2.2 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-unreal@v3.2.2 | https://github.com/apigear-io/template-unreal.git | | v3.2.2 ... ``` ``` $ apigear template install apigear-io/template-cpp14@v3.6.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-cpp14@v3.6.0 | https://github.com/apigear-io/template-cpp14.git | | v3.6.0 ... ``` ``` $ apigear template install apigear-io/template-qtcpp@v0.4.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-qtcpp@v0.4.0 | https://github.com/apigear-io/template-qtcpp.git | | v0.4.0 ... ``` ``` $ apigear template install apigear-io/template-python@v1.0.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-python@v1.0.0 | https://github.com/apigear-io/template-python.git | | v1.0.0 ... ``` ### Installation via Studio[​](#installation-via-studio "Direct link to Installation via Studio") Installing the template through the Studio is straightforward: * Unreal Engine * C++14 * Qt6 * Python 1. Open an existing project or create a new one 2. Navigate to the `Templates` tab 3. Click `Install` on the `apigear-io/template-unreal` entry 1) Open an existing project or create a new one 2) Navigate to the `Templates` tab 3) Click `Install` on the `apigear-io/template-cpp14` entry 1. Open an existing project or create a new one 2. Navigate to the `Templates` tab 3. Click `Install` on the `apigear-io/template-qtcpp` entry 1) Open an existing project or create a new one 2) Navigate to the `Templates` tab 3) Click `Install` on the `apigear-io/template-python` entry ![Installing the template](/img/apigear-studio-install-unreal-template.png) Figure: Installing the template ### Cloning from GitHub[​](#cloning-from-github "Direct link to Cloning from GitHub") If you need to inspect or modify the template's source code, cloning or downloading the repository is recommended. The repository doesn't need to be part of your project and can be stored anywhere on your computer. * Unreal Engine * C++14 * Qt6 * Python ``` $ git clone https://github.com/apigear-io/template-unreal.git ``` ``` $ git clone https://github.com/apigear-io/template-cpp14.git ``` ``` $ git clone https://github.com/apigear-io/template-qtcpp.git ``` ``` $ git clone https://github.com/apigear-io/template-python.git ``` You can then configure the solution file to use your template by providing the relative path from the solution file to the template directory. ## 3. Set Up the Project[​](#3-set-up-the-project "Direct link to 3. Set Up the Project") A typical project requires two files: a solution file specifying the APIs and the template to use, and at least one API module file. Ideally, both should be placed in a folder named `apigear`, adjacent to each other. Alternatively, you can use the Studio to create a new project and modify the two example files provided. ### Solution File[​](#solution-file "Direct link to Solution File") Create a [solution](/docs/guide/quick-start.md#solution-file) file. The example below specifies: * Module files in *line 8*, here the `helloworld.module.yaml` module with the `Hello` API * The output directory for generated files in *line 9* * The template used to generate the code in *line 10* (this can also be a path to a local copy of the template) * The enabled features of the template in *line 13*, here the `stubs` feature, which provides a simple implementation of interfaces. - Unreal Engine - C++14 - Qt6 - Python helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: ue_docs inputs: - helloworld.module.yaml output: ../ue_docs template: apigear-io/template-unreal@v3.2.2 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: cpp_hello_world inputs: - helloworld.module.yaml output: ../cpp_hello_world template: apigear-io/template-cpp14@v3.6.0 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: qt_hello_world inputs: - helloworld.module.yaml output: ../qt_hello_world template: apigear-io/template-qtcpp@v0.4.0 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: hello_world inputs: - helloworld.module.yaml output: ../py_hello_world template: apigear-io/template-python@v1.0.0 force: true features: - stubs ``` Targets You can extend this solution file with additional targets, each for a different technology with a different template. The `module.yaml` file is technology-independent and can be used with any template. Template Version We highly recommend specifying the exact version of the template. Otherwise, a newer version will be automatically downloaded and used for code generation. This can lead to unexpected behavior if file structures or code changes in the template. note Set the `force` parameter to `true` if you want to always override all generated files. When set to `false`, some files, such as implementations (stub feature), won't be updated. API files are always updated regardless of this setting. ### API Module File[​](#api-module-file "Direct link to API Module File") Use your preferred text editor to create the `helloworld.module.yaml` file with the following example content: helloworld.module.yaml ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` ## 4. Generate Code[​](#4-generate-code "Direct link to 4. Generate Code") ### Generate via CLI[​](#generate-via-cli "Direct link to Generate via CLI") The following snippet demonstrates how to run the CLI and provides an example output: ``` $ apigear generate solution apigear/helloworld.solution.yaml 10:52:20 INF generated 21 files in 30ms. (20 write, 0 skip, 1 copy) topic=gen ``` * The `generate` command instructs the CLI to generate code * The `solution` parameter specifies that we want to process a solution file ### Generate via Studio[​](#generate-via-studio "Direct link to Generate via Studio") 1. Open the project 2. Navigate to the `Solutions` tab 3. Click `Run` on the `helloworld.solution.yaml` entry ![Generating code](/img/apigear-studio-generate-code.png) Figure: Generating code ## 5. How to use the plugin[​](#5-how-to-use-the-plugin "Direct link to 5. How to use the plugin") ### Project folder structure[​](#project-folder-structure "Direct link to Project folder structure") For the code generation we assume that both *ApiGear* files reside in an `apigear` subfolder next to the *Unreal Engine* project. In this case the folder structure should look similar to this. ``` 📦ue_docs_example_project ┣ 📂apigear ┃ ┣ 📜helloworld.solution.yaml ┃ ┗ 📜helloworld.module.yaml ┣ 📂ue_docs ┃ ┣ 📂Config ┃ ┣ 📂Content ┃ ┣ 📂Platforms ┃ ┣ 📂Plugins ┃ ┣ 📂Source ┃ ┗ 📜ue_docs.uproject ``` Using the solution file from the previous paragraph the code will be generated in the `ue_docs/Plugins` folder. Solution `output:` path The generator writes each module's plugin folder **directly** under `output:` — it does not append `Plugins/` for you. To land plugins under your Unreal project's `Plugins/` directory, set `output: ../ue_docs/Plugins` in your `solution.yaml`. The path you give is the parent of the generated plugin folders (e.g. `IoWorld/`, `ApiGear/`). Prerequisites This guide assumes you already have an Unreal Engine project. The generated plugin will be placed in your project's `Plugins` folder. The generated code can be used with *Blueprints* and *C++*. Make sure to have the generated plugin code in your projects plugins directory as noted in the [previous section](#4-generate-code). ### Hosting the plugin in a fresh UE project[​](#hosting-the-plugin-in-a-fresh-ue-project "Direct link to Hosting the plugin in a fresh UE project") If you don't have an existing Unreal project (for example, in a CI pipeline or when starting from a clean workspace), the four files below are the minimum needed to host a generated plugin. The example uses the `IoWorld` plugin produced from `io.world.Hello` and references it from a host module called `HelloHost`. Drop these files next to the `Plugins/` directory you generated into: ``` 📦HelloHost ┣ 📂Plugins ┃ ┣ 📂ApiGear ┃ ┗ 📂IoWorld ┣ 📂Source ┃ ┣ 📂HelloHost ┃ ┃ ┗ 📜HelloHost.Build.cs ┃ ┣ 📜HelloHost.Target.cs ┃ ┗ 📜HelloHostEditor.Target.cs ┗ 📜HelloHost.uproject ``` #### `HelloHost.uproject`[​](#hellohostuproject "Direct link to hellohostuproject") HelloHost.uproject ``` { "FileVersion": 3, "EngineAssociation": "5.7", "Category": "", "Description": "ApiGear MQTT5 helloworld host project", "Modules": [ { "Name": "HelloHost", "Type": "Runtime", "LoadingPhase": "Default", "AdditionalDependencies": [ "Engine", "IoWorldAPI", "IoWorldCore", "IoWorldImplementation", "IoWorldMQTT", "ApiGear", "ApiGearMQTT", "ApiGearMQTTPaho" ] } ], "Plugins": [ { "Name": "ApiGear", "Enabled": true }, { "Name": "IoWorld", "Enabled": true } ] } ``` #### `Source/HelloHost.Target.cs`[​](#sourcehellohosttargetcs "Direct link to sourcehellohosttargetcs") Source/HelloHost.Target.cs ``` using UnrealBuildTool; public class HelloHostTarget : TargetRules { public HelloHostTarget(TargetInfo Target) : base(Target) { Type = TargetType.Game; DefaultBuildSettings = BuildSettingsVersion.V6; IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7; ExtraModuleNames.Add("HelloHost"); } } ``` #### `Source/HelloHostEditor.Target.cs`[​](#sourcehellohosteditortargetcs "Direct link to sourcehellohosteditortargetcs") Source/HelloHostEditor.Target.cs ``` using UnrealBuildTool; public class HelloHostEditorTarget : TargetRules { public HelloHostEditorTarget(TargetInfo Target) : base(Target) { Type = TargetType.Editor; DefaultBuildSettings = BuildSettingsVersion.V6; IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7; ExtraModuleNames.Add("HelloHost"); } } ``` #### `Source/HelloHost/HelloHost.Build.cs`[​](#sourcehellohosthellohostbuildcs "Direct link to sourcehellohosthellohostbuildcs") The dependency list is the non-obvious bit. `IoWorldAPI` / `IoWorldCore` / `IoWorldImplementation` are always needed; add `IoWorldMQTT`, `ApiGearMQTT`, and `ApiGearMQTTPaho` only if you generated the `mqtt` feature. Swap in `IoWorldOLink` / `ApiGearOLink` for OLink, or `IoWorldMsgBus` for Message Bus. Source/HelloHost/HelloHost.Build.cs ``` using UnrealBuildTool; public class HelloHost : ModuleRules { public HelloHost(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; IWYUSupport = IWYUSupport.Full; PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "IoWorldAPI", "IoWorldCore", "IoWorldImplementation", "IoWorldMQTT", "ApiGear", "ApiGearMQTT", "ApiGearMQTTPaho", "JsonUtilities", "Projects" }); PrivateDependencyModuleNames.AddRange(new string[] { }); } } ``` You also need a minimal module entry point — two short files next to `HelloHost.Build.cs`: Source/HelloHost/HelloHost.h ``` #pragma once #include "CoreMinimal.h" ``` Source/HelloHost/HelloHost.cpp ``` #include "HelloHost.h" #include "Modules/ModuleManager.h" IMPLEMENT_PRIMARY_GAME_MODULE(FDefaultGameModuleImpl, HelloHost, "HelloHost"); ``` After dropping these files in place, right-click the `.uproject` and choose **Generate Visual Studio project files** (or run UnrealBuildTool's `-projectfiles` command), then build the editor target. The generated plugins compile alongside your host module. ### Verify Plugin Installation[​](#verify-plugin-installation "Direct link to Verify Plugin Installation") After generating the code: 1. Open your Unreal project in the Editor 2. Go to **Edit → Plugins** 3. Search for `IoWorld` - you should see the generated plugin listed 4. Ensure the plugin is enabled (checkbox checked) 5. Restart the Editor if prompted If the plugin does not appear, verify the generated files are in the correct `Plugins` folder and regenerate project files. ### Using the Interface[​](#using-the-interface "Direct link to Using the Interface") * Blueprint * C++ #### Getting the Subsystem[​](#getting-the-subsystem "Direct link to Getting the Subsystem") Open your project in the Unreal Editor and choose a *Blueprint* where you want to use the interface. First, get the *GameInstance* subsystem of the type `IoWorldHelloImplementation`: ![Get subsystem in Blueprint](/img/quick-start/quick-start-blueprint-get-subsystem.png) Figure: Get subsystem in Blueprint #### Calling Operations[​](#calling-operations "Direct link to Calling Operations") On this subsystem we can choose the *asynchronous Say* function. Using the async version prevents blocking the game thread: ![Choose method on interface in Blueprint](/img/quick-start/quick-start-blueprint-choose-method.png) Figure: Choose method on interface in Blueprint #### Complete Example[​](#complete-example "Direct link to Complete Example") The complete setup to say "Hello world" on begin play: ![Hello interface example in Blueprint](/img/quick-start/quick-start-blueprint-complete.png) Figure: Hello interface example in Blueprint #### Module Dependencies[​](#module-dependencies "Direct link to Module Dependencies") For any C++ module to access the generated interface, add the dependency in your `.Build.cs` file. The module name is a PascalCase transformation of the ApiGear module name (`io.world` becomes `IoWorld`): ue\_docs.Build.cs ``` PrivateDependencyModuleNames.AddRange(new string[] { "IoWorldAPI", "IoWorldImplementation" }); ``` After modifying your `.Build.cs` file, regenerate your project files and rebuild the project. In Visual Studio, this is typically done via **Build → Rebuild Solution**. #### Include Headers[​](#include-headers "Direct link to Include Headers") The generated plugin provides three key headers: * **IoWorldHello.h** - The implementation class (GameInstance subsystem) * **IoWorld\_data.h** - Data types: structs (`FIoWorldMessage`) and enums (`EIoWorldWhen`) * **IoWorldHelloInterface.h** - The interface definition (`IIoWorldHelloInterface`) For most use cases, include all three: ``` #include "IoWorld/Implementation/IoWorldHello.h" #include "IoWorld/Generated/api/IoWorld_data.h" #include "IoWorld/Generated/api/IoWorldHelloInterface.h" ``` #### Calling Operations[​](#calling-operations-1 "Direct link to Calling Operations") Get the implementation through the GameInstance subsystem. The `TScriptInterface` wrapper provides polymorphic access to the interface while holding a reference to the concrete implementation. Enum values use a prefix derived from the module and enum name. For the `When` enum in `io.world`, the prefix is `IWW_` (IoWorld When), giving values like `IWW_Now`, `IWW_Soon`, and `IWW_Never`. ue\_docsGameModeBase.cpp ``` #include "ue_docsGameModeBase.h" #include "IoWorld/Implementation/IoWorldHello.h" #include "IoWorld/Generated/api/IoWorld_data.h" #include "IoWorld/Generated/api/IoWorldHelloInterface.h" void Aue_docsGameModeBase::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) { Super::InitGame(MapName, Options, ErrorMessage); TScriptInterface Hello = GetGameInstance()->GetSubsystem(); FIoWorldMessage MyMsg; MyMsg.content = FString("Hello world"); Hello->Say(MyMsg, EIoWorldWhen::IWW_Now); } ``` #### Async Operations (Recommended)[​](#async-operations-recommended "Direct link to Async Operations (Recommended)") Operations with return values have async variants that prevent blocking the game thread. This is especially important when using network backends (OLink, MsgBus) where operations involve network latency. The async call returns immediately with a `TFuture` that resolves when the operation completes. ``` TFuture Future = Hello->SayAsync(MyMsg, EIoWorldWhen::IWW_Now); Future.Next([](const int32& Result) { UE_LOG(LogTemp, Log, TEXT("Say returned: %d"), Result); }); ``` ### Subscribing to Events[​](#subscribing-to-events "Direct link to Subscribing to Events") * Blueprint * C++ To react to property changes or signals in Blueprints: 1. Get the subsystem as shown above 2. Call **Get Publisher** on the subsystem 3. From the Publisher, drag off and search for **Assign** to find bindable events 4. Choose the event you want: **On Last Changed BP** (property change) or **On Just Said Signal BP** (signal) The Publisher object holds all delegates because Unreal interfaces cannot declare delegates directly. note Blueprint screenshots for event binding coming soon. To receive property change and signal notifications, bind to delegates on the Publisher object. Callback methods must be marked with `UFUNCTION()` for dynamic delegate binding to work. Delegate names follow these patterns: * Property changes: `On{PropertyName}ChangedBP` (e.g., `OnLastChangedBP`) * Signals: `On{SignalName}SignalBP` (e.g., `OnJustSaidSignalBP`) In your class header, declare the callback functions: ``` UFUNCTION() void OnLastChanged(const FIoWorldMessage& Last); UFUNCTION() void OnJustSaid(const FIoWorldMessage& Msg); ``` In your implementation, bind to the events: ``` Hello->_GetPublisher()->OnLastChangedBP.AddDynamic(this, &UMyClass::OnLastChanged); Hello->_GetPublisher()->OnJustSaidSignalBP.AddDynamic(this, &UMyClass::OnJustSaid); ``` caution Always remove delegate bindings when your object is destroyed. Failing to do so can cause crashes when the event fires after your object is gone: ``` Hello->_GetPublisher()->OnLastChangedBP.RemoveDynamic(this, &UMyClass::OnLastChanged); ``` --- # Introduction ApiGear generates native SDKs from **stateful API definitions**. Define your interfaces once using ObjectAPI, then generate production-ready code for C++, Qt, Unreal Engine, or Python. ## Stateful APIs for Real-World Systems[​](#stateful-apis-for-real-world-systems "Direct link to Stateful APIs for Real-World Systems") Unlike REST or RPC specifications that model stateless requests, ObjectAPI models interfaces as **objects with observable state** — properties that change over time, methods to call, and signals (events) pushed from server to client. This matches how systems actually work in automotive, gaming, IoT, and embedded domains where state matters. ``` interface Thermostat { temperature: float // property: observable state setTarget(float temp) // operation: method call signal overheated() // signal: server-pushed event } ``` ## In 3 Steps[​](#in-3-steps "Direct link to In 3 Steps") 1. **Define your API** using the ObjectAPI IDL or YAML format 2. **Run the generator** with your chosen template (C++14, C++17, Qt6, Unreal, Python) 3. **Get complete, buildable code** — interfaces, stubs, tests, and build files ## Key Concepts[​](#key-concepts "Direct link to Key Concepts") | Term | What it means | | ----------------- | ------------------------------------------------------------------------------------------- | | **ObjectAPI** | A specification for stateful, object-oriented APIs with properties, operations, and signals | | **IDL Format** | A concise, developer-friendly syntax for writing APIs (auto-converts to YAML) | | **YAML Format** | The canonical format for API modules | | **Solution File** | A config file specifying which modules to generate and which template to use | | **Template** | A code generator for a specific platform (e.g., `template-cpp17`, `template-unreal`) | ## Why ApiGear?[​](#why-apigear "Direct link to Why ApiGear?") ### Designed for Real Systems[​](#designed-for-real-systems "Direct link to Designed for Real Systems") * **Stateful by design** — Properties with change notifications, not just request/response. This is how real systems work in automotive, gaming, and IoT. * **Protocol agnostic** — Your API definition is independent of transport. Generate code that works over WebSockets, IPC, or custom protocols without changing your interface. * **Built for complex domains** — Trusted in automotive HMI, game engines, industrial IoT, and embedded systems where reliability and performance matter. ### Accelerate Development[​](#accelerate-development "Direct link to Accelerate Development") * **Parallel development** — Frontend and backend teams work simultaneously. UI developers use simulated backends while services are still being built. * **Eliminate integration bugs** — No more mismatched types, missing fields, or API drift. The generated code guarantees both sides speak the same language. * **Reduce boilerplate by 80%** — Stop writing repetitive serialization, networking, and observer code. Focus on business logic instead. ### One Source of Truth[​](#one-source-of-truth "Direct link to One Source of Truth") * **Define once, generate everywhere** — A single API definition produces native SDKs for C++, Qt, Unreal Engine, Python, and more. * **Consistent patterns** — Every generated SDK follows the same architecture. Learn one, understand all. * **Version your APIs** — Track API evolution in git. Review changes in pull requests. Roll back when needed. ### Production-Ready Output[​](#production-ready-output "Direct link to Production-Ready Output") * **Complete, buildable code** — Not just interfaces, but stubs, tests, build files, and examples. Run your first test minutes after generation. * **Built-in simulation** — Test against scripted backends before real services exist. Validate edge cases, error handling, and timing scenarios. * **Real-time monitoring** — Debug with visibility into every property change, method call, and signal across your system. ### Extensible & Open[​](#extensible--open "Direct link to Extensible & Open") * **Open source** — Backed by Epic Games. Inspect the code, contribute improvements, or fork for your needs. * **Write your own templates** — Need a custom target platform? The template system is fully extensible with Jinja2. * **Active community** — Get help on Slack, discuss on GitHub, or contribute to the ecosystem. ## How does it work?[​](#how-does-it-work "Direct link to How does it work?") The following steps provide a high-level overview of the ApiGear workflow. For more detailed information, please refer to our [Quick Start](/docs/guide/quick-start.md) section. ### Development Cycle Overview[​](#development-cycle-overview "Direct link to Development Cycle Overview") ![Developer Cycle](/assets/images/devcycle_light-f521e6094e307c5e453639b3e814ba2c.svg#light) ![Developer Cycle](/assets/images/devcycle_dark-b481b45da22d2b7a412e1e922bb463c3.svg#dark) The graphic above illustrates the development cycle using ApiGear. Like all modern development tools, ApiGear fully supports an agile development approach. You can begin with a minimal API definition and iteratively add functionality or modify existing details. While it's theoretically possible to completely rewrite the API, adopting an *API-first approach* will help you design more stable and high-quality APIs from the outset. ### 1. Define API[​](#1-define-api "Direct link to 1. Define API") Following the *API-first approach*, you begin by defining your [ObjectAPI module](/docs/objectapi/modules.md). This can be accomplished using either the [ApiGear Studio](/docs/studio/intro.md) or the [ApiGear CLI](/docs/cli/intro.md). An API module is a concise document that adheres to the ObjectAPI specification. ### 2. Generate SDK[​](#2-generate-sdk "Direct link to 2. Generate SDK") Once you have created one or more API modules, you're ready to generate your first SDK. This process involves creating a solution file where you specify an output folder and select one of our expertly engineered [SDK templates](/docs/sdk/intro.md). ### 3. Apply Changes[​](#3-apply-changes "Direct link to 3. Apply Changes") The output folder contains various files, depending on the SDK solution file setup, selected template, and features. If you opt for the "full package including scaffolding," you'll receive the following types of files: 1. The plain API in your preferred programming language 2. A stub implementation derived from this API with basic functionality 3. A comprehensive suite of test files covering the stub implementation 4. Project files to instantly build the entire API module and execute the test cases 5. Additional adapters created by ApiGear to provide API analytics insights 6. Examples demonstrating the usage of the generated code ### 4. Create Application[​](#4-create-application "Direct link to 4. Create Application") With the API module in place, you can begin integrating it into your application. Depending on whether the same team or a different team is implementing the application, they can utilize the module with a stub implementation, a simulation adapter, or the actual business logic. This approach eliminates dependencies, allowing the business logic on the service side and the user application to be developed in parallel, based on the commonly defined API description. ### 5. Gain Insights[​](#5-gain-insights "Direct link to 5. Gain Insights") The upcoming *ApiGear Analytics* feature will provide comprehensive insights into your customers' API usage. This valuable feedback enables you to make informed decisions about which features to extend or optimize, enhancing efficiency and customer satisfaction. --- # Code Generation The `generate` command creates SDKs from ObjectAPI definitions using templates. It supports solution-based generation for project workflows and expert mode for fine-grained control. ## Commands[​](#commands "Direct link to Commands") | Command | Alias | Description | | ------------------- | ---------------- | ------------------------------ | | `generate solution` | `gen sol`, `g s` | Generate from a solution file | | `generate expert` | `gen x` | Generate with explicit options | ## Solution-Based Generation[​](#solution-based-generation "Direct link to Solution-Based Generation") The recommended approach uses a solution file that defines inputs, outputs, templates, and features. ### Basic Usage[​](#basic-usage "Direct link to Basic Usage") ``` apigear generate solution apigear/demo.solution.yaml ``` ### Solution File Format[​](#solution-file-format "Direct link to Solution File Format") ``` schema: "apigear.solution/1.0" name: my_project version: "1.0.0" targets: - name: cpp_sdk inputs: - myapi.module.yaml output: ../generated/cpp template: apigear-io/template-cpp14@v3.6.0 force: true features: - api - stubs - olink ``` ### Solution Options[​](#solution-options "Direct link to Solution Options") | Field | Description | | ---------- | -------------------------------------------- | | `name` | Target identifier | | `inputs` | List of module files (YAML or IDL) | | `output` | Output directory (relative to solution file) | | `template` | Template name with optional version | | `force` | Overwrite existing files (default: false) | | `features` | List of template features to enable | ### Watch Mode[​](#watch-mode "Direct link to Watch Mode") Automatically regenerate when the solution file changes: ``` apigear generate solution demo.solution.yaml --watch ``` ### Force Overwrite[​](#force-overwrite "Direct link to Force Overwrite") Override the solution's `force` setting: ``` apigear generate solution demo.solution.yaml --force ``` ## Expert Mode[​](#expert-mode "Direct link to Expert Mode") For quick generation without a solution file, use expert mode with explicit options. ### Basic Usage[​](#basic-usage-1 "Direct link to Basic Usage") ``` apigear generate expert -i demo.module.yaml -o output -t apigear-io/template-cpp14 ``` ### Expert Options[​](#expert-options "Direct link to Expert Options") | Flag | Short | Description | Default | | ------------ | ----- | ----------------------------- | ------------- | | `--input` | `-i` | Input module files (required) | `["apigear"]` | | `--output` | `-o` | Output directory (required) | `"out"` | | `--template` | `-t` | Template to use (required) | `"tpl"` | | `--features` | `-f` | Features to enable | `["all"]` | | `--force` | — | Overwrite existing files | `false` | | `--watch` | — | Watch for changes | `false` | ### Multiple Inputs[​](#multiple-inputs "Direct link to Multiple Inputs") ``` apigear generate expert \ -i api/user.module.yaml \ -i api/inventory.module.yaml \ -o generated \ -t apigear-io/template-cpp14 ``` ### Enabling Features[​](#enabling-features "Direct link to Enabling Features") ``` apigear generate expert \ -i demo.module.yaml \ -o output \ -t apigear-io/template-cpp14 \ -f api \ -f stubs \ -f olink ``` ### Using Local Templates[​](#using-local-templates "Direct link to Using Local Templates") Point to a local template directory: ``` apigear generate expert \ -i demo.module.yaml \ -o output \ -t ./my-custom-template ``` ## Template Versioning[​](#template-versioning "Direct link to Template Versioning") ### Latest Version[​](#latest-version "Direct link to Latest Version") Without a version specifier, the latest version is used: ``` apigear generate expert -t apigear-io/template-cpp14 ... ``` ### Specific Version[​](#specific-version "Direct link to Specific Version") Pin to a specific version using `@version`: ``` apigear generate expert -t apigear-io/template-cpp14@v3.6.0 ... ``` ``` # In solution file template: apigear-io/template-cpp14@v3.6.0 ``` tip Always specify versions in production to ensure reproducible builds. ## Multiple Targets[​](#multiple-targets "Direct link to Multiple Targets") A solution can generate multiple SDKs from the same API: ``` targets: - name: cpp_sdk inputs: - myapi.module.yaml output: ../generated/cpp template: apigear-io/template-cpp14@v3.6.0 features: - api - stubs - name: python_sdk inputs: - myapi.module.yaml output: ../generated/python template: apigear-io/template-python@v1.0.0 features: - api - stubs - name: unreal_sdk inputs: - myapi.module.yaml output: ../generated/unreal template: apigear-io/template-unreal@v3.2.2 features: - api - stubs ``` ## Common Features[​](#common-features "Direct link to Common Features") Most templates support these features: | Feature | Description | | ---------- | ----------------------------------- | | `api` | Generate interface definitions | | `stubs` | Generate implementation stubs | | `scaffold` | Generate complete project structure | | `olink` | Enable ObjectLink protocol support | | `mqtt` | Enable MQTT protocol support | | `monitor` | Enable API monitoring | Check individual template documentation for available features. ## Input Formats[​](#input-formats "Direct link to Input Formats") ### YAML Format[​](#yaml-format "Direct link to YAML Format") ``` # demo.module.yaml schema: apigear.module/1.0 name: demo version: "1.0.0" interfaces: - name: Counter properties: - name: count type: int operations: - name: increment - name: decrement ``` ### IDL Format[​](#idl-format "Direct link to IDL Format") ``` // demo.idl module demo 1.0 interface Counter { count: int increment() decrement() } ``` Both formats can be used interchangeably in inputs. ## Output Structure[​](#output-structure "Direct link to Output Structure") Generated output follows the template's structure. A typical C++ output: ``` generated/ ├── CMakeLists.txt ├── demo/ │ ├── api/ │ │ ├── counter.h │ │ └── counter.cpp │ ├── implementation/ │ │ ├── counter.h │ │ └── counter.cpp │ └── olink/ │ ├── counter_service.h │ └── counter_client.h └── README.md ``` ## CI/CD Usage[​](#cicd-usage "Direct link to CI/CD Usage") ``` # Install specific template version apigear template install apigear-io/template-cpp14@v3.6.0 # Generate with pinned versions apigear generate solution apigear/solution.yaml # Check exit code if [ $? -ne 0 ]; then echo "Generation failed" exit 1 fi ``` ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Template Not Found[​](#template-not-found "Direct link to Template Not Found") ``` Error: template not found: apigear-io/template-cpp14 ``` Install the template first: ``` apigear template install apigear-io/template-cpp14 ``` ### Invalid Module[​](#invalid-module "Direct link to Invalid Module") ``` Error: invalid module: demo.module.yaml ``` Validate the module: ``` apigear spec check demo.module.yaml ``` ### Output Directory Exists[​](#output-directory-exists "Direct link to Output Directory Exists") By default, existing files are not overwritten. Use `--force` or set `force: true` in the solution. --- # Introduction The `apigear` command-line interface (CLI) is the primary tool for working with the ApiGear platform. It provides complete functionality for API-driven development, from code generation to runtime monitoring and simulation. ## Key Features[​](#key-features "Direct link to Key Features") ### Code Generation[​](#code-generation "Direct link to Code Generation") Generate production-ready SDKs from ObjectAPI definitions using templates for C++, Qt, Unreal Engine, Python, and more. ### Project Management[​](#project-management "Direct link to Project Management") Create, organize, and manage API projects with modules, solutions, and scenarios. ### Template Management[​](#template-management "Direct link to Template Management") Browse, install, and manage SDK templates from the ApiGear registry or create custom templates. ### API Simulation[​](#api-simulation "Direct link to API Simulation") Run scripted backends that simulate your services for client development and testing. ### API Stimulation[​](#api-stimulation "Direct link to API Stimulation") Drive your service implementations with scripted client calls for validation and load testing. ### API Monitoring[​](#api-monitoring "Direct link to API Monitoring") Monitor real-time API traffic from your applications for debugging and validation. ## Installation[​](#installation "Direct link to Installation") Download the latest release for your platform from the [GitHub releases page](https://github.com/apigear-io/cli/releases). ### macOS[​](#macos "Direct link to macOS") ``` # Download and extract curl -L https://github.com/apigear-io/cli/releases/latest/download/apigear-darwin-amd64.tar.gz | tar xz # Move to PATH sudo mv apigear /usr/local/bin/ ``` ### Linux[​](#linux "Direct link to Linux") ``` # Download and extract curl -L https://github.com/apigear-io/cli/releases/latest/download/apigear-linux-amd64.tar.gz | tar xz # Move to PATH sudo mv apigear /usr/local/bin/ ``` ### Windows[​](#windows "Direct link to Windows") Download the `.exe` file from the releases page and add it to your PATH. ## Quick Start[​](#quick-start "Direct link to Quick Start") ``` # Check installation apigear version # Create a new project apigear project create -d my-api-project # Generate code from a solution apigear generate solution apigear/demo.solution.yaml # Start simulation server apigear sim run apigear/demo.scenario.js # Monitor API traffic apigear monitor run ``` ## Command Overview[​](#command-overview "Direct link to Command Overview") | Command | Alias | Description | | ----------- | ---------- | ---------------------------------- | | `generate` | `gen`, `g` | Generate SDKs from API definitions | | `project` | `prj` | Manage API projects | | `template` | `tpl`, `t` | Manage SDK templates | | `simulate` | `sim`, `s` | Run scripted backend simulations | | `stimulate` | `stim` | Run scripted client stimulations | | `monitor` | `mon`, `m` | Monitor API traffic | | `olink` | `ol` | Interactive ObjectLink REPL | | `mcp` | — | Start MCP server for AI assistants | | `spec` | — | Validate API specifications | | `config` | `cfg`, `c` | Manage configuration | | `serve` | — | Start combined server | | `update` | — | Update CLI to latest version | | `version` | — | Display version information | ## Updating[​](#updating "Direct link to Updating") The CLI can update itself to the latest version: ``` apigear update ``` Use `--force` to skip confirmation: ``` apigear update --force ``` ## Configuration[​](#configuration "Direct link to Configuration") View current configuration: ``` apigear config info ``` Display all configuration values: ``` apigear config get ``` Export environment variables: ``` apigear config env ``` ### Configuration Locations[​](#configuration-locations "Direct link to Configuration Locations") | Platform | Path | | -------- | ---------------------------------------- | | macOS | `~/Library/Application Support/apigear/` | | Linux | `~/.config/apigear/` | | Windows | `%APPDATA%\apigear\` | ### Environment Variables[​](#environment-variables "Direct link to Environment Variables") | Variable | Description | | ------------------- | -------------------------------- | | `APIGEAR_TEMPLATES` | Override template cache location | | `APIGEAR_REGISTRY` | Override template registry URL | ## Server Ports[​](#server-ports "Direct link to Server Ports") Default ports used by CLI servers: | Service | Default Port | Purpose | | ---------- | ------------ | ------------------------------- | | Simulation | 4333 | WebSocket server for ObjectLink | | Monitor | 5555 | HTTP server for API events | | NATS | 4222 | Message broker (optional) | ## Document Validation[​](#document-validation "Direct link to Document Validation") Validate any ApiGear document: ``` apigear spec check myapi.module.yaml ``` View schema for document types: ``` apigear spec schema --type module apigear spec schema --type solution apigear spec schema --type rules ``` ## Experimental Features[​](#experimental-features "Direct link to Experimental Features") Access experimental commands with the `x` command: ``` # Convert between formats apigear x json2yaml file.json apigear x yaml2json file.yaml apigear x yaml2idl api.module.yaml apigear x idl2yaml api.idl # WebSocket testing apigear x wsecho --address :8080 apigear x wscat --url ws://localhost:8080 ``` ## CI/CD Integration[​](#cicd-integration "Direct link to CI/CD Integration") The CLI is designed for automation: ``` # Non-interactive update apigear update --force # Generate with specific template version apigear generate solution solution.yaml # Watch mode for development apigear generate solution solution.yaml --watch ``` ## Getting Help[​](#getting-help "Direct link to Getting Help") Get help for any command: ``` apigear --help apigear generate --help apigear template install --help ``` ## Related Documentation[​](#related-documentation "Direct link to Related Documentation") * [Code Generation](/docs/cli/generate.md) — Generate SDKs from API definitions * [Project Management](/docs/cli/project.md) — Create and manage projects * [SDK Templates](/docs/cli/template.md) — Manage SDK templates * [API Simulation](/docs/cli/simulate.md) — Run scripted backends * [API Monitoring](/docs/cli/monitor.md) — Monitor API traffic * [ObjectLink REPL](/docs/cli/olink.md) — Interactive protocol testing * [MCP Server](/docs/cli/mcp.md) — AI assistant integration via Model Context Protocol --- # MCP Server The `apigear mcp` command starts a [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server that exposes ApiGear CLI functionality as tools for AI assistants. This allows tools like Claude Code, Cursor, and other MCP-compatible clients to generate SDKs, manage templates, and validate specifications through natural language. ## Setup[​](#setup "Direct link to Setup") ### Claude Code[​](#claude-code "Direct link to Claude Code") Register the MCP server with Claude Code: ``` claude mcp add apigear -- apigear mcp ``` ### Other MCP Clients[​](#other-mcp-clients "Direct link to Other MCP Clients") Add the following to your MCP client configuration: ``` { "mcpServers": { "apigear": { "command": "apigear", "args": ["mcp"] } } } ``` ## Available Tools[​](#available-tools "Direct link to Available Tools") The MCP server exposes 7 tools organized by category. Each tool declares an **annotation** so MCP clients know whether to prompt for confirmation: | Annotation | Meaning | | ----------- | -------------------------------------------------------------------- | | read-only | Only reads data, makes no changes — safe to run without confirmation | | destructive | Writes files to disk, may overwrite existing content | | idempotent | Can be called repeatedly with the same result | ### Specification[​](#specification "Direct link to Specification") | Tool | Annotation | Description | | --------------------- | ---------- | ------------------------------------------------------------------------------- | | `specificationCheck` | read-only | Load and validate API specification files (module, solution, rules) | | `specificationSchema` | read-only | Show the schema for module, solution, or rules documents in YAML or JSON format | **specificationCheck Parameters** | Parameter | Required | Description | | --------- | -------- | -------------------------- | | `file` | Yes | Path to specification file | **specificationSchema Parameters** | Parameter | Required | Description | | --------- | -------- | --------------------------------------------- | | `type` | Yes | Document type (`module`, `solution`, `rules`) | | `format` | Yes | Output format (`yaml`, `json`) | **Example prompts:** > "Check if my `demo.module.yaml` is valid." > > "Show me the module schema in YAML format." ### Generation[​](#generation "Direct link to Generation") | Tool | Annotation | Description | | ------------------ | ----------- | ------------------------------------------------------- | | `generateSolution` | destructive | Generate SDK based on a solution document | | `generateExpert` | destructive | Generate code using expert mode with individual options | **generateSolution Parameters** | Parameter | Required | Description | | ---------- | -------- | ---------------------------------- | | `solution` | Yes | Path to solution file | | `force` | No | Force overwrite (`true`/`false`) | | `watch` | No | Watch for changes (`true`/`false`) | **generateExpert Parameters** | Parameter | Required | Description | | ---------- | -------- | ------------------------------------------------------- | | `input` | Yes | Input module files (comma-separated) | | `output` | Yes | Output directory | | `template` | Yes | Template directory | | `features` | No | Features to enable (comma-separated, defaults to `all`) | | `force` | No | Force overwrite (`true`/`false`) | | `watch` | No | Watch for changes (`true`/`false`) | **Example prompts:** > "Generate code from `apigear/demo.solution.yaml`." > > "Generate a C++14 SDK from `demo.module.yaml` using `apigear-io/template-cpp14`, output to `generated/cpp`, enable the `api` and `stubs` features." ### Template[​](#template "Direct link to Template") | Tool | Annotation | Description | | ---------------- | ---------- | ------------------------------------------------ | | `templateList` | read-only | List available templates from the registry | | `templateUpdate` | idempotent | Update the template registry from remote sources | Both template tools take no parameters. **Example prompts:** > "Show me all available ApiGear templates." > > "Update the template registry." ### Utility[​](#utility "Direct link to Utility") | Tool | Annotation | Description | | --------- | ---------- | ------------------------------- | | `version` | read-only | Display CLI version information | The `version` tool takes no parameters. **Example prompt:** > "What version of ApiGear CLI is installed?" ## Related Documentation[​](#related-documentation "Direct link to Related Documentation") * [AI Meets API Design: MCP-Powered SDK Generation](/blog/ai-meets-api-design-mcp.md) — blog walkthrough: connect ApiGear to your AI assistant step by step * [Code Generation](/docs/cli/generate.md) — Generate SDKs from API definitions * [SDK Templates](/docs/cli/template.md) — Manage SDK templates * [Introduction](/docs/cli/intro.md) — CLI overview and installation --- # API Monitoring The `monitor` command runs a server that receives and displays API events from your applications. Use it to debug API interactions, verify correct behavior, and understand API usage patterns. ## Commands[​](#commands "Direct link to Commands") | Command | Alias | Description | | -------------- | -------------------- | ------------------------ | | `monitor run` | `mon r`, `mon start` | Start the monitor server | | `monitor feed` | — | Feed events from a file | ## Starting the Monitor[​](#starting-the-monitor "Direct link to Starting the Monitor") ### Basic Usage[​](#basic-usage "Direct link to Basic Usage") ``` apigear monitor run ``` This starts the monitor server on the default address `http://localhost:5555`. ### Options[​](#options "Direct link to Options") | Flag | Short | Description | Default | | ------------- | ----- | ------------------------- | ----------------------- | | `--nats-url` | `-n` | NATS server URL | `nats://localhost:4222` | | `--verbose` | `-v` | Enable verbose logging | `false` | | `--device-id` | `-d` | Filter by device ID | `""` (all devices) | | `--pretty` | `-p` | Pretty print JSON output | `false` | | `--headers` | `-H` | Include headers in output | `false` | ### Examples[​](#examples "Direct link to Examples") Pretty-print all events: ``` apigear monitor run --pretty ``` Monitor specific device: ``` apigear monitor run --device-id my-device-123 ``` Verbose mode with headers: ``` apigear monitor run --verbose --headers ``` ## Monitor Endpoint[​](#monitor-endpoint "Direct link to Monitor Endpoint") Applications send events to the monitor via HTTP POST: ``` POST http://localhost:5555/monitor/{source} Content-Type: application/json { "type": "method", "interface": "demo.Counter", "method": "increment", "args": [], "timestamp": "2024-01-15T10:30:00Z" } ``` The `{source}` path parameter identifies the event source (e.g., device ID, service name). ## Event Format[​](#event-format "Direct link to Event Format") Monitor events follow this structure: ``` { "type": "property|method|signal", "interface": "module.Interface", "property": "propertyName", "method": "methodName", "signal": "signalName", "value": {}, "args": [], "result": {}, "timestamp": "2024-01-15T10:30:00Z" } ``` ### Property Events[​](#property-events "Direct link to Property Events") ``` { "type": "property", "interface": "demo.Counter", "property": "count", "value": 42 } ``` ### Method Events[​](#method-events "Direct link to Method Events") ``` { "type": "method", "interface": "demo.Counter", "method": "increment", "args": [], "result": 43 } ``` ### Signal Events[​](#signal-events "Direct link to Signal Events") ``` { "type": "signal", "interface": "demo.Counter", "signal": "countChanged", "args": [43] } ``` ## Feeding Test Data[​](#feeding-test-data "Direct link to Feeding Test Data") Use `monitor feed` to replay events from files for testing. ### Supported Formats[​](#supported-formats "Direct link to Supported Formats") | Format | Extension | Description | | ---------- | --------- | ------------------------------- | | JSON | `.json` | Single event or array of events | | NDJSON | `.ndjson` | Newline-delimited JSON events | | JavaScript | `.js` | Script that generates events | | CSV | `.csv` | Comma-separated event data | ### Basic Usage[​](#basic-usage-1 "Direct link to Basic Usage") ``` apigear monitor feed events.ndjson ``` ### Feed Options[​](#feed-options "Direct link to Feed Options") | Flag | Description | Default | | ------------ | ------------------------ | ----------------------- | | `--url` | Monitor server address | `http://localhost:5555` | | `--repeat` | Times to repeat the file | `1` | | `--interval` | Delay between events | `100ms` | | `--device` | Device ID to use | `"123"` | | `--batch` | Events per batch | `1` | ### Examples[​](#examples-1 "Direct link to Examples") Repeat events 10 times: ``` apigear monitor feed events.ndjson --repeat 10 ``` Faster playback: ``` apigear monitor feed events.ndjson --interval 10ms ``` Batch events: ``` apigear monitor feed events.ndjson --batch 5 ``` ### NDJSON Format[​](#ndjson-format "Direct link to NDJSON Format") ``` {"type":"property","interface":"demo.Counter","property":"count","value":0} {"type":"method","interface":"demo.Counter","method":"increment","args":[]} {"type":"property","interface":"demo.Counter","property":"count","value":1} {"type":"signal","interface":"demo.Counter","signal":"countChanged","args":[1]} ``` ## SDK Integration[​](#sdk-integration "Direct link to SDK Integration") Enable monitoring in generated SDKs by including the `monitor` feature: ``` # solution.yaml targets: - name: cpp_sdk inputs: - demo.module.yaml output: ../generated template: apigear-io/template-cpp14 features: - api - stubs - monitor # Enable monitoring ``` The generated code wraps your implementation with monitoring hooks that send events to the monitor server. ### Configuring the Monitor URL[​](#configuring-the-monitor-url "Direct link to Configuring the Monitor URL") In your application, configure the monitor endpoint: ``` // C++ example auto monitor = ApiGear::Monitor::create("http://localhost:5555/monitor/my-app"); auto counter = Counter::create(monitor); ``` ``` # Python example monitor = apigear.Monitor("http://localhost:5555/monitor/my-app") counter = Counter(monitor=monitor) ``` ## Use Cases[​](#use-cases "Direct link to Use Cases") ### Development Debugging[​](#development-debugging "Direct link to Development Debugging") Watch API calls during development: ``` # Terminal 1: Start monitor apigear monitor run --pretty # Terminal 2: Run your application ./my-app ``` ### Integration Testing[​](#integration-testing "Direct link to Integration Testing") Verify expected API behavior: ``` # Start monitor and capture output apigear monitor run > api-events.log & # Run test suite ./run-tests # Analyze captured events cat api-events.log | jq '.interface' ``` ### Load Testing[​](#load-testing "Direct link to Load Testing") Feed high-volume test data: ``` apigear monitor feed load-test.ndjson \ --repeat 1000 \ --interval 1ms \ --batch 10 ``` ### Demo and Presentation[​](#demo-and-presentation "Direct link to Demo and Presentation") Visualize API activity in Studio: 1. Start monitor: `apigear monitor run` 2. Open ApiGear Studio 3. Navigate to Monitor view 4. Run your application ## Combining with Simulation[​](#combining-with-simulation "Direct link to Combining with Simulation") Run simulation and monitor together: ``` # Terminal 1: Start simulation server apigear sim run scenario.js # Terminal 2: Start monitor apigear monitor run --pretty # Terminal 3: Run client application ./my-client ``` ## NATS Integration[​](#nats-integration "Direct link to NATS Integration") For distributed systems, use NATS as a message broker: ``` # Start NATS server (if not running) nats-server # Start monitor with NATS apigear monitor run --nats-url nats://localhost:4222 ``` Events are published to NATS subjects based on interface names. ## Related Documentation[​](#related-documentation "Direct link to Related Documentation") * [Monitoring APIs](/docs/monitor/intro.md) — Detailed monitoring documentation * [API Simulation](/docs/cli/simulate.md) — Run scripted backends * [ObjectLink REPL](/docs/cli/olink.md) — Interactive protocol testing --- # ObjectLink REPL The `apigear olink` command starts an interactive REPL (Read-Eval-Print Loop) for testing the ObjectLink protocol. It provides a command-line interface to connect to ObjectLink servers, link to remote objects, invoke methods, set properties, and observe signals. ## Overview[​](#overview "Direct link to Overview") ObjectLink is an IPC protocol that enables linking local objects to remote objects over network connections. The REPL allows developers to interactively test and debug ObjectLink implementations by providing direct access to the protocol's core operations. ## Usage[​](#usage "Direct link to Usage") Start the ObjectLink REPL: ``` apigear olink ``` or using the alias: ``` apigear ol ``` ## REPL Commands[​](#repl-commands "Direct link to REPL Commands") Once the REPL is started, you can use various commands to interact with ObjectLink servers. All commands support aliases for faster typing. ### Connection Management[​](#connection-management "Direct link to Connection Management") **Connect to a server:** ``` > connect ``` Connects to the default WebSocket server at `ws://localhost:5555/ws`. ``` > connect ws://localhost:8080/ws ``` Connects to a custom WebSocket server. **Disconnect:** ``` > disconnect ``` Disconnects from the current server. ### Object Operations[​](#object-operations "Direct link to Object Operations") **Link to a remote object:** ``` > link demo.Counter ``` Links to the `demo.Counter` object on the remote server. **Get object properties:** ``` > get demo.Counter ``` Retrieves all properties of the linked object. **Set a property:** ``` > set demo.Counter/count 10 ``` Sets the `count` property of `demo.Counter` to `10`. **Invoke a method:** ``` > invoke demo.Counter/increment ``` Calls the `increment` method without arguments. ``` > invoke demo.Calculator/add 3 7 ``` Calls the `add` method with arguments `[3, 7]`. **Unlink from an object:** ``` > unlink demo.Counter ``` Unlinks from the `demo.Counter` object. ### Utility Commands[​](#utility-commands "Direct link to Utility Commands") **Show connection status:** ``` > info ``` Displays current connection and object registry status. **Get help:** ``` > help ``` Shows all available commands. ``` > help connect ``` Shows detailed help for the `connect` command. **Exit the REPL:** ``` > quit ``` Exits the ObjectLink REPL. ## Example Session[​](#example-session "Direct link to Example Session") Here's a complete example session demonstrating typical ObjectLink REPL usage: ``` $ apigear olink ObjectLink REPL started. Type 'help' for available commands. > connect Connected to ws://localhost:5555/ws > link demo.Counter Linked to demo.Counter > get demo.Counter demo.Counter: {"count": 0} > invoke demo.Counter/increment Method invoked successfully > get demo.Counter demo.Counter: {"count": 1} > set demo.Counter/count 5 Property set successfully > invoke demo.Counter/increment Method invoked successfully > get demo.Counter demo.Counter: {"count": 6} > unlink demo.Counter Unlinked from demo.Counter > quit Goodbye! ``` ## Command Aliases[​](#command-aliases "Direct link to Command Aliases") Most commands have short aliases for convenience: * `c` = `connect` * `d` = `disconnect` * `s` = `serve` * `l` = `link` * `u` = `unlink` * `g` = `get` * `i` = `invoke` * `h` = `help` * `q` = `quit` ## Protocol Features[​](#protocol-features "Direct link to Protocol Features") The ObjectLink REPL supports all core ObjectLink protocol features: * **Properties**: Reactive and synchronized between local and remote objects * **Operations**: Request/response communication for method calls * **Signals**: Event notifications from remote objects to local clients * **Modules**: Namespace organization for objects * **Lifecycle Management**: Link/unlink operations for object relationships ## Use Cases[​](#use-cases "Direct link to Use Cases") The ObjectLink REPL is particularly useful for: * **Development**: Testing ObjectLink implementations during development * **Debugging**: Diagnosing issues with ObjectLink communication * **Learning**: Understanding ObjectLink protocol behavior * **Integration Testing**: Verifying client-server interactions ## Related Documentation[​](#related-documentation "Direct link to Related Documentation") * [ObjectLink Protocol](/docs/protocols/objectlink/intro.md) - Complete protocol specification * [API Generation](/docs/cli/generate.md) - Generate ObjectLink-enabled SDKs * [API Monitoring](/docs/cli/monitor.md) - Monitor ObjectLink traffic --- # Project Management The `project` command allows you to manage your projects. Projects are a collection of APIs stored in module files and solution file with desired generation configuration. You can create, list, add files for your projects. ## Create a new project[​](#create-a-new-project "Direct link to Create a new project") To create a project use the `create` command. ``` apigear project create -d ``` you can prefix a project name with a path in which you'd like your project to be created. This command creates a folder with the apigear files in it: * `module` an example file type with the example interface description * `solution` a solution file used for generation configuration * `simulation` with example simulation for the example interface ## Project info[​](#project-info "Direct link to Project info") Gives basic project information along with list of files in this project. ``` apigear project info ``` ## Show recent projects[​](#show-recent-projects "Direct link to Show recent projects") Lists all the recently used projects ``` apigear project recent ``` ## Edit project[​](#edit-project "Direct link to Edit project") Use the `edit` command to open all your project files in your default editor. ``` apigear project edit ``` ## Open project in studio[​](#open-project-in-studio "Direct link to Open project in studio") To open project in studio use the `open` command. ``` apigear project open ``` ## Project configuration[​](#project-configuration "Direct link to Project configuration") The `project` command stores the project configuration in the `/.apigear` file. The configuration file contains the following information. ``` # .apigear { "folder": "apigear", } ``` The `folder` property specifies the folder where the project configuration is stored. The default value is `apigear`. ## Add a File[​](#add-a-file "Direct link to Add a File") The `add` command allows you to create documents in the project ``` apigear project ``` The document will be created in the `apigear` folder of the current project. The `document-type` can be one of the following values: `module`, `solution` or `scenario`. To choose a project directory (by default it uses current directory) us `-p your-path-to-project` argument. ## Pack a Project[​](#pack-a-project "Direct link to Pack a Project") The `pack` command allows you to pack a project. It will create an archive file containing all project documents. ``` apigear project pack -d ``` ## Import a Project[​](#import-a-project "Direct link to Import a Project") The `import` command allows you to import a project form a github repository. ``` apigear project import -t ``` --- # Scripting Support The CLI provides JavaScript-based scripting for both simulating services (scripted backends) and driving services (scripted clients). This enables testing, prototyping, and validation without compiled code. ## Commands[​](#commands "Direct link to Commands") | Command | Alias | Description | | --------------- | -------- | ------------------------------ | | `simulate run` | `sim r` | Run a scripted backend | | `simulate feed` | `sim f` | Feed simulation data from file | | `stimulate run` | `stim r` | Run a scripted client | ## Scripted Backends (Simulation)[​](#scripted-backends-simulation "Direct link to Scripted Backends (Simulation)") Simulate your service to test client code before the real backend exists. ### Basic Usage[​](#basic-usage "Direct link to Basic Usage") ``` apigear sim run scenario.js ``` ### Options[​](#options "Direct link to Options") | Flag | Description | Default | | --------------- | -------------------------- | ----------------------- | | `--fn` | Function to run on startup | `main` | | `--watch` | Watch script for changes | `false` | | `--nats-server` | NATS server URL | `nats://localhost:4222` | ### Watch Mode[​](#watch-mode "Direct link to Watch Mode") Automatically reload when the script changes: ``` apigear sim run scenario.js --watch ``` ### Example Script[​](#example-script "Direct link to Example Script") ``` // counter.js const counter = $createService("demo.Counter", { count: 0 }); counter.increment = function() { counter.count++; return counter.count; } counter.decrement = function() { counter.count--; return counter.count; } // React to property changes counter.$.onProperty("count", function(value) { console.log("count changed to", value); }); function main() { console.log("Counter service started"); console.log("Initial count:", counter.count); } ``` Run it: ``` apigear sim run counter.js ``` The server listens on `ws://localhost:4333/ws` by default. ## Scripted Clients (Stimulation)[​](#scripted-clients-stimulation "Direct link to Scripted Clients (Stimulation)") Drive your running service with scripted API calls. ### Basic Usage[​](#basic-usage-1 "Direct link to Basic Usage") ``` apigear stim run client.js ``` ### Options[​](#options-1 "Direct link to Options") | Flag | Description | Default | | --------- | -------------------------- | ------- | | `--fn` | Function to run on startup | `main` | | `--watch` | Watch script for changes | `false` | ### Example Script[​](#example-script-1 "Direct link to Example Script") ``` // client.js const channel = $createChannel("ws://localhost:4333/ws"); const counter = channel.createClient("demo.Counter"); // Monitor property changes counter.onProperty("count", function(value) { console.log("count is now:", value); }); function main() { console.log("Client started"); // Call remote methods for (let i = 0; i < 5; i++) { counter.callMethod("increment"); } console.log("Done"); } ``` Run it: ``` apigear stim run client.js ``` ## Service API[​](#service-api "Direct link to Service API") Create simulated services with the `$createService` function. ### Creating a Service[​](#creating-a-service "Direct link to Creating a Service") ``` const service = $createService("module.Interface", { // Initial property values propertyName: initialValue }); ``` ### Properties[​](#properties "Direct link to Properties") ``` // Set property service.count = 10; // Get property console.log(service.count); // Using bare API service.$.setProperty("count", 10); service.$.getProperty("count"); // React to changes service.$.onProperty("count", function(value) { console.log("count changed:", value); }); ``` ### Methods[​](#methods "Direct link to Methods") ``` // Define method implementation service.increment = function() { service.count++; return service.count; }; // Using bare API service.$.setMethod("increment", function() { return service.count++; }); ``` ### Signals[​](#signals "Direct link to Signals") ``` // Emit signal service.$.emitSignal("countChanged", [service.count]); // Listen for signals service.$.onSignal("countChanged", function(args) { console.log("Signal received:", args); }); ``` ## Client API[​](#client-api "Direct link to Client API") Connect to remote services with the `$createChannel` function. ### Creating a Channel[​](#creating-a-channel "Direct link to Creating a Channel") ``` // Default address const channel = $createChannel(); // Custom address const channel = $createChannel("ws://localhost:5555/ws"); ``` ### Creating a Client[​](#creating-a-client "Direct link to Creating a Client") ``` const client = channel.createClient("module.Interface"); ``` ### Calling Methods[​](#calling-methods "Direct link to Calling Methods") ``` client.callMethod("methodName"); client.callMethod("methodName", arg1, arg2); ``` ### Monitoring Properties[​](#monitoring-properties "Direct link to Monitoring Properties") ``` client.onProperty("propertyName", function(value) { console.log("Property changed:", value); }); ``` ### Listening for Signals[​](#listening-for-signals "Direct link to Listening for Signals") ``` client.onSignal("signalName", function(args) { console.log("Signal received:", args); }); ``` ## Global Functions[​](#global-functions "Direct link to Global Functions") Available in all scripts: | Function | Description | | --------------------------------- | --------------------------------- | | `$createService(name, props)` | Create a simulated service | | `$createBareService(name, props)` | Create service with bare API only | | `$createChannel(url?)` | Create client channel | | `$quit()` | Exit the script | | `console.log(...)` | Print to console | | `setTimeout(fn, ms)` | Delayed execution | | `setInterval(fn, ms)` | Repeated execution | ## Feeding Data[​](#feeding-data "Direct link to Feeding Data") Feed pre-recorded data to simulation: ``` apigear sim feed events.ndjson ``` ### Feed Options[​](#feed-options "Direct link to Feed Options") | Flag | Description | Default | | ---------- | ---------------------- | ------------------------ | | `--addr` | Server address | `ws://127.0.0.1:4333/ws` | | `--repeat` | Times to repeat | `1` | | `--sleep` | Delay between messages | `100ms` | | `--batch` | Messages per batch | `1` | ### NDJSON Format[​](#ndjson-format "Direct link to NDJSON Format") ``` {"type":"property","interface":"demo.Counter","property":"count","value":0} {"type":"method","interface":"demo.Counter","method":"increment"} {"type":"signal","interface":"demo.Counter","signal":"countChanged","args":[1]} ``` ## Complex Example[​](#complex-example "Direct link to Complex Example") A physics simulation with position, velocity, and acceleration: ``` // ball.js const ball = $createService("demo.Ball", { pos: { x: 0, y: 0 }, vel: { x: 1, y: 1 }, acc: { x: 0.1, y: 0.1 } }); ball.move = function() { // Update velocity ball.vel = { x: ball.vel.x + ball.acc.x, y: ball.vel.y + ball.acc.y }; // Update position ball.pos = { x: ball.pos.x + ball.vel.x, y: ball.pos.y + ball.vel.y }; return ball.pos; }; ball.reset = function() { ball.pos = { x: 0, y: 0 }; ball.vel = { x: 1, y: 1 }; }; ball.$.onProperty("pos", function(value) { console.log("Position:", JSON.stringify(value)); }); function main() { console.log("Ball simulation started"); // Run 10 steps for (let i = 0; i < 10; i++) { ball.move(); } console.log("Final state:", JSON.stringify(ball.$.getProperties())); $quit(); } ``` ## Use Cases[​](#use-cases "Direct link to Use Cases") ### Client Development[​](#client-development "Direct link to Client Development") Test UI code against a simulated backend: ``` # Start simulation apigear sim run backend.js # Run your client application ./my-client-app ``` ### Load Testing[​](#load-testing "Direct link to Load Testing") Drive your service with repeated calls: ``` apigear stim run load-test.js ``` ``` // load-test.js const channel = $createChannel(); const service = channel.createClient("demo.Service"); function main() { for (let i = 0; i < 1000; i++) { service.callMethod("process", { id: i }); } } ``` ### Integration Testing[​](#integration-testing "Direct link to Integration Testing") Verify service behavior: ``` apigear sim run mock-service.js & apigear stim run test-client.js ``` ## Related Documentation[​](#related-documentation "Direct link to Related Documentation") * [Scripted Backends](/docs/scripting/backends/intro.md) — Detailed backend scripting * [Scripted Clients](/docs/scripting/clients/intro.md) — Detailed client scripting * [ObjectLink Protocol](/docs/protocols/objectlink/intro.md) — Communication protocol --- # SDK Templates The CLI allows you to fully manage the SDK templates. You can install, update and remove templates. The templates are stored in a local folder on your drive. The default location is `~/.apigear/templates`. You can change the location using the `APIGEAR_TEMPLATES` environment variable. ## List Available Templates[​](#list-available-templates "Direct link to List Available Templates") You can list all templates available in apigear registry using the `apigear template list`. Those will be installed in your local cache if used with `generate` command (both quick generation or in a solution file). ``` $ apigear template list template | git-url apigear-io/template-cpp14 | https://github.com/apigear-io/template-cpp14.git apigear-io/template-go | https://github.com/apigear-io/template-go.git apigear-io/template-python | https://github.com/apigear-io/template-python.git apigear-io/template-qt5 | https://github.com/apigear-io/template-qt5.git apigear-io/template-qtcpp | https://github.com/apigear-io/template-qtcpp.git apigear-io/template-unreal | https://github.com/apigear-io/template-unreal.git ``` ## Check Installed Templates[​](#check-installed-templates "Direct link to Check Installed Templates") Use template command with a `cache` parameter to get list of all installed templates with their versions. ``` $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-cpp14@v3.4.0 | https://github.com/apigear-io/template-cpp14.git | a47fb50405215c520cf40527e1057cd4cc6d5e0e | v3.4.0 apigear-io/template-cpp14@v3.6.0 | https://github.com/apigear-io/template-cpp14.git | 9f262863ddd60858c7db02ff6829a709d88ce1e5 | v3.6.0 apigear-io/template-unreal@v3.2.2 | https://github.com/apigear-io/template-unreal.git | da8164860de23b703335f1b96583c5c78609cbe5 | v3.2.2 ``` ## Install a Template[​](#install-a-template "Direct link to Install a Template") To install a template you need to know the name of the template. The name is the same as the name of the template in the [ApiGear Registry](https://github.com/apigear-io/registry). You can find the name in the template details page or just list all available templates. ``` apigear template install ``` The `name` argument is the name of the template. The command will install the template in the local template folder. The default location is `~/.apigear/templates`. You can change the location using the `APIGEAR_TEMPLATES` environment variable. By default the newest available version of template is isntalled, to specify exact version use `@version` right after the `name`. # Template Info You can get information about a template using the `apigear template info` command. The command will print the details of the template. ``` $ apigear template info apigear-io/template-cpp14 Name: apigear-io/template-cpp14 URL: https://github.com/apigear-io/template-cpp14.git Version: Latest: v3.6.0 Versions: v3.6.0, v3.5.2, v3.5.1, v3.5.0, v3.4.0, v3.3.0, v3.2.0, v3.1.1, v3.1.0, v3.0.0, v2.1.1, v2.1.0-0, v2.1.0 ``` The information includes the name, version, url of the template. # Update a Template To update a template you need to know the name of the template. The name is the same as the name of the template in the ApiGear Registry. You can find the name in the template details page. ``` apigear template update ``` The update will update to the latest version by default but it's also possible to specify a version. ## Remove a Template[​](#remove-a-template "Direct link to Remove a Template") You can remove a template by name. The name can be found using the `cache` command. ``` apigear template remove ``` or you can remove all installed templates with ``` apigear template clean ``` --- # Calculator API Demo A calculator allows you to enter numbers and calculate a new value based on different operations. Our calculator will support `add`, `subtract`, `multiply` and `divide` operations as well as a `clear` action. The calculator always has a total value to show the current calculated value. A typical frontend would show a field to display the current total value and a grid of numbers from 0 to 9 as well as a grid for the operations `+`, `-`, `*`, `/` and the `clear` action. tip In addition to the explanation below we also created some real modules for different languages and example applications. Please check out our [demos over at GitHub](https://github.com/apigear-io/objectapi-demos/tree/master/calc). ## Defining an API Module[​](#defining-an-api-module "Direct link to Defining an API Module") From inside a project we create a new API module called `demo.calc` using the `Create Module` action and create a new interface named 'Calculator'. ``` schema: apigear.module/1.0 name: demo.calc version: "1.0" interfaces: - name: Calculator ``` A typical calculator always displays the current value calculated. We will add this as a property to the interface. ``` interfaces: - name: Calculator properties: - name: value type: int ``` We expect the value is always updated when a number was entered and a new operation is entered. We add the first operation `add` to the calculator. It takes one parameter and the number to be added. ``` interfaces: - name: Calculator operations: - name: add params: - name: a type: int ``` Let's add the rest of the calculator operations and the clear action. ``` schema: apigear.module/1.0 name: demo.calc version: "1.0" interfaces: - name: Calculator properties: - name: value type: int operations: - name: add params: - name: a type: int - name: subtract params: - name: a type: int - name: multiply params: - name: a type: int - name: divide params: - name: a type: int - name: clear ``` We added all missing calculator operations. The clear action will reset the calculator value to `0`. ## Python Interface[​](#python-interface "Direct link to Python Interface") For our implementation we configure a Python SDK as runner and generate our calculator SDK. The abstract calculator interface will look like this: ``` # demo_calc.py class AbstractCalculator(object): def __init__(self): self.value = 0 def add(a: int): raise NotImplementedError def subtract(a: int): raise NotImplementedError def multiply(a: int): raise NotImplementedError def divide(a: int): raise NotImplementedError def clear(a: int): raise NotImplementedError ``` ## Sample Implementation[​](#sample-implementation "Direct link to Sample Implementation") To implement the interface we derive from the abstract calculator and implement the missing operations. A simple implementation could look like this. ``` # import our generated interfaces import demo_calc class Calculator(demo_calc.AbstractCalculator): def add(a: int): self.value += a def subtract(a: int): self.value -= a def multiply(a: int): self.value *= a def divide(a: int): self.value /= a def clear(a: int): self.value = 0 ``` ## API Updates[​](#api-updates "Direct link to API Updates") The next time we change the API module we need to update the abstract interface and ensure all changed operations are implemented. It is very easy now to add implementations also for other programming languages or even for a micro service using one of the many supported SDK templates. --- # Counter API Demo The example we are looking at is a simple coffee counter. Our product will track the daily coffee consumption of a user. For this the user interface requires an API to the backend to count coffees. From the frontend perspective we have a button to increase the coffee count and a total score of the coffees we had a day. We assume the backend automatically resets the counter every night. For this we design an API which provides the coffee count for that day as well as an action to record a new coffee consumption. This API module will reside inside a team project. As an API module resides inside a project, we will create a project named "*daily coffee*" and provide a catchy brief description: "*count the coffee the user consumes per day*". When the project is created we are forwarded to the project page. A default API module is already created for us. We can delete this, as we do not need it for this project. We create a new API module called `demo.daily` now. A project can have more than one API module. Configured code generators to create API SDKs are also attached to a project and the API simulations, which are auto updated with the API changes. Now we enter the API definition into the API editor field and save the content. ``` schema: apigear.module/1.0 name: demo.daily version: "1.0" interfaces: - name: Counter properties: - name: count type: int operations: - name: increment ``` The API consists of an interface located in the `demo.daily` module. The `Counter` interface itself consist of a property `count` of the type integer. The interface also exposes an operation named `increment`. For more information on the API definition language see the [ObjectAPI guide](/docs/objectapi/intro.md). We create a C++14 API for this tutorial. When we open the SDK section, we select the C++14 SDK and press run. After we downloaded the created SDK, we unzip the source code in our terminal and run the project. Detailed information how to run the project will be in the README file of your SDK. ## Using the API[​](#using-the-api "Direct link to Using the API") The generated code will result in a C++ class with some abstract methods. The API has no functionality yet, and needs to be provided by you. So open the project in your favorite editor and add the implementation. ``` // counter.h void Counter::increment() { // add implementation here count++; } ``` Now, you can use the class in your main function. ``` // main.cpp #include using namespace std; #include "counter.h" int main(int argc, char**argv) { Counter counter; cout << counter.count(); counter.increment(); cout << counter.count(); } ``` Which concrete API you will use depends on the code generators you configure. There are code-generators for many languages and technologies available. We can now continue to design the API inside the APIGear's API editor. The editor allows us to save the API at any point and to create a new version if required. ## Documentation[​](#documentation "Direct link to Documentation") To make the API more descriptive we can add some description. The API language has full support for markdown based descriptions. To write code examples just indent the code snippets ``` schema: apigear.module/1.0 name: demo.daily version: "1.0" description: A module to demonstrate an API interfaces: - name: Counter description: An interface to increment a coffee counter properties: - name: count type: int description: holds the current count value operations: - name: increment description: increments the count value ``` After saving the API module, we can see the documentation in the API portal. It is automatically updated, based on the API module content. Now, we have successfully defined an API. The next step will be to use the API. First in a playground like environment, later in a real project. --- # Quick Start This Quick-Start guide demonstrates how to progress from an API definition to a functional example in just a few steps. It provides a streamlined introduction to using ApiGear for your project. This quick start guide focuses on enabling basic features: the `api` interface generation and a simple `stub` implementation. ## 1. Install the Code Generator[​](#1-install-the-code-generator "Direct link to 1. Install the Code Generator") ApiGear provides two powerful tools: a Studio and a Command Line Interface (CLI). The Studio is a graphical tool for creating, editing API modules and projects, while the CLI is a command-line tool for generating code from API modules. Both offer comprehensive functionality, including SDK template management, API project creation, code generation, API monitoring, and API simulation. [Download Studio](https://github.com/apigear-io/studio/releases/latest) [Download CLI](https://github.com/apigear-io/cli/releases/latest) ### Install ApiGear Studio[​](#install-apigear-studio "Direct link to Install ApiGear Studio") ApiGear Studio is available for macOS, Windows, and Linux. You can download it from the [ApiGear Studio GitHub releases page](https://github.com/apigear-io/studio/releases/latest). For more detailed information on ApiGear Studio, refer to the dedicated [studio documentation](/docs/studio/intro.md). ### Install ApiGear CLI[​](#install-apigear-cli "Direct link to Install ApiGear CLI") The ApiGear CLI is a versatile command-line tool for generating code from API modules. It's available for macOS, Windows, and Linux. Download the latest version from the [ApiGear CLI GitHub releases page](https://github.com/apigear-io/cli/releases/latest). The CLI offers all the features available in the Studio. For more detailed information on the ApiGear CLI, consult the dedicated [CLI documentation](/docs/cli/intro.md). ## 2. Obtain the Template[​](#2-obtain-the-template "Direct link to 2. Obtain the Template") There are several methods to obtain the template: installation via the Studio, installation via the CLI, or cloning/downloading from GitHub. This step is optional The ApiGear Studio and the CLI detect the specified template in the [solution document](#solution-file) and install it automatically. Optional steps (click to expand) ### Installation via CLI[​](#installation-via-cli "Direct link to Installation via CLI") When using the CLI, only the highlighted line needs to be executed. You can verify the successful installation using the `template cache` command. * Unreal Engine * C++14 * Qt6 * Python ``` $ apigear template install apigear-io/template-unreal@v3.2.2 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-unreal@v3.2.2 | https://github.com/apigear-io/template-unreal.git | | v3.2.2 ... ``` ``` $ apigear template install apigear-io/template-cpp14@v3.6.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-cpp14@v3.6.0 | https://github.com/apigear-io/template-cpp14.git | | v3.6.0 ... ``` ``` $ apigear template install apigear-io/template-qtcpp@v0.4.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-qtcpp@v0.4.0 | https://github.com/apigear-io/template-qtcpp.git | | v0.4.0 ... ``` ``` $ apigear template install apigear-io/template-python@v1.0.0 $ apigear template cache list of templates from the local cache source | url | installed | latest apigear-io/template-python@v1.0.0 | https://github.com/apigear-io/template-python.git | | v1.0.0 ... ``` ### Installation via Studio[​](#installation-via-studio "Direct link to Installation via Studio") Installing the template through the Studio is straightforward: * Unreal Engine * C++14 * Qt6 * Python 1. Open an existing project or create a new one 2. Navigate to the `Templates` tab 3. Click `Install` on the `apigear-io/template-unreal` entry 1) Open an existing project or create a new one 2) Navigate to the `Templates` tab 3) Click `Install` on the `apigear-io/template-cpp14` entry 1. Open an existing project or create a new one 2. Navigate to the `Templates` tab 3. Click `Install` on the `apigear-io/template-qtcpp` entry 1) Open an existing project or create a new one 2) Navigate to the `Templates` tab 3) Click `Install` on the `apigear-io/template-python` entry ![Installing the template](/img/apigear-studio-install-unreal-template.png) Figure: Installing the template ### Cloning from GitHub[​](#cloning-from-github "Direct link to Cloning from GitHub") If you need to inspect or modify the template's source code, cloning or downloading the repository is recommended. The repository doesn't need to be part of your project and can be stored anywhere on your computer. * Unreal Engine * C++14 * Qt6 * Python ``` $ git clone https://github.com/apigear-io/template-unreal.git ``` ``` $ git clone https://github.com/apigear-io/template-cpp14.git ``` ``` $ git clone https://github.com/apigear-io/template-qtcpp.git ``` ``` $ git clone https://github.com/apigear-io/template-python.git ``` You can then configure the solution file to use your template by providing the relative path from the solution file to the template directory. ## 3. Set Up the Project[​](#3-set-up-the-project "Direct link to 3. Set Up the Project") A typical project requires two files: a solution file specifying the APIs and the template to use, and at least one API module file. Ideally, both should be placed in a folder named `apigear`, adjacent to each other. Alternatively, you can use the Studio to create a new project and modify the two example files provided. ### Solution File[​](#solution-file "Direct link to Solution File") Create a [solution](/docs/guide/quick-start.md#solution-file) file. The example below specifies: * Module files in *line 8*, here the `helloworld.module.yaml` module with the `Hello` API * The output directory for generated files in *line 9* * The template used to generate the code in *line 10* (this can also be a path to a local copy of the template) * The enabled features of the template in *line 13*, here the `stubs` feature, which provides a simple implementation of interfaces. - Unreal Engine - C++14 - Qt6 - Python helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: ue_docs inputs: - helloworld.module.yaml output: ../ue_docs template: apigear-io/template-unreal@v3.2.2 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: cpp_hello_world inputs: - helloworld.module.yaml output: ../cpp_hello_world template: apigear-io/template-cpp14@v3.6.0 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: qt_hello_world inputs: - helloworld.module.yaml output: ../qt_hello_world template: apigear-io/template-qtcpp@v0.4.0 force: true features: - stubs ``` helloworld.solution.yaml ``` schema: "apigear.solution/1.0" name: hello_world_example version: "0.1.0" targets: - name: hello_world inputs: - helloworld.module.yaml output: ../py_hello_world template: apigear-io/template-python@v1.0.0 force: true features: - stubs ``` Targets You can extend this solution file with additional targets, each for a different technology with a different template. The `module.yaml` file is technology-independent and can be used with any template. Template Version We highly recommend specifying the exact version of the template. Otherwise, a newer version will be automatically downloaded and used for code generation. This can lead to unexpected behavior if file structures or code changes in the template. note Set the `force` parameter to `true` if you want to always override all generated files. When set to `false`, some files, such as implementations (stub feature), won't be updated. API files are always updated regardless of this setting. ### API Module File[​](#api-module-file "Direct link to API Module File") Use your preferred text editor to create the `helloworld.module.yaml` file with the following example content: helloworld.module.yaml ``` schema: apigear.module/1.0 name: io.world version: "1.0.0" interfaces: - name: Hello properties: - { name: last, type: Message } operations: - name: say params: - { name: msg, type: Message } - { name: when, type: When } return: type: int signals: - name: justSaid params: - { name: msg, type: Message } enums: - name: When members: - { name: Now, value: 0 } - { name: Soon, value: 1 } - { name: Never, value: 2 } structs: - name: Message fields: - { name: content, type: string } ``` ## 4. Generate Code[​](#4-generate-code "Direct link to 4. Generate Code") ### Generate via CLI[​](#generate-via-cli "Direct link to Generate via CLI") The following snippet demonstrates how to run the CLI and provides an example output: ``` $ apigear generate solution apigear/helloworld.solution.yaml 10:52:20 INF generated 21 files in 30ms. (20 write, 0 skip, 1 copy) topic=gen ``` * The `generate` command instructs the CLI to generate code * The `solution` parameter specifies that we want to process a solution file ### Generate via Studio[​](#generate-via-studio "Direct link to Generate via Studio") 1. Open the project 2. Navigate to the `Solutions` tab 3. Click `Run` on the `helloworld.solution.yaml` entry ![Generating code](/img/apigear-studio-generate-code.png) Figure: Generating code ## 5. Use the Generated SDK[​](#5-use-the-generated-sdk "Direct link to 5. Use the Generated SDK") The application structure and usage will vary depending on the technology template you've chosen. Please use the following links to navigate to the appropriate section for your selected technology: * Unreal Engine * C++14 * Qt6 * Python [How to use the generated Unreal Engine plugin code](/template-unreal/docs/quickstart.md?current-template=template-unreal#5-how-to-use-the-plugin) [How to use the generated C++14 code](/template-cpp14/docs/quickstart.md?current-template=template-cpp14#5-use-the-generated-cpp-project) [How to use the generated Qt6 code](/template-qt6/docs/quickstart.md?current-template=template-qtcpp#5-use-the-generated-qt-project) [How to use the generated Python code](/template-python/docs/quickstart.md?current-template=template-python#5-use-the-generated-python-project) --- # Update Strategy When working with generated code over time, having a clear strategy for handling API updates helps maintain a clean codebase and simplifies integration of changes. tip We strongly recommend using a source code [version control](https://wikipedia.org/wiki/Version_control) system, such as [Git](https://git-scm.com/). Maintaining a clean workspace state simplifies the integration of API updates into existing code. ## Initial Setup[​](#initial-setup "Direct link to Initial Setup") To set up your project for long-term API updates: 1. Generate the code into an *initial* folder within your project 2. Copy the *initial* folder to a *solution* folder 3. Commit this state as the initial version to enable rollbacks if needed You can then replace the API stub implementation in the *solution* folder with your business logic and update the test stubs to cover the actual API behavior. ## Updating Existing APIs[​](#updating-existing-apis "Direct link to Updating Existing APIs") When you've set up your project using the *initial* and *solution* folder structure (or a similar setup), you can easily apply updates to your API: 1. Generate the updated SDK into the *initial* folder. A diff in your preferred source control tool should only show the auto-generated changes based on your API modifications. 2. Use a *compare and merge* tool to review the differences between the updated *initial* folder and your existing implementation in the *solution* folder. 3. Apply only the interface changes without overwriting your business logic. While this process may seem cumbersome at first, it's straightforward and easy to use in practice. note When using a version control system, you could generate the code directly in the final location. However, this approach may become challenging over time, depending on the project size and the number of manually added or modified files in the output folder. ## Alternative: Force Mode[​](#alternative-force-mode "Direct link to Alternative: Force Mode") If you prefer a simpler workflow and don't need to preserve manual changes in generated files, you can use the `force: true` setting in your solution file: ``` targets: - name: my_target inputs: - myapi.module.yaml output: ../output template: apigear-io/template-cpp14 force: true # Always overwrite generated files features: - stubs ``` With `force: true`, all generated files are overwritten on each generation. This works well when: * You keep business logic separate from generated code * You use the generated code as a starting point and maintain it manually afterward * You're in early development and API changes are frequent warning When `force` is set to `false`, some files (such as stub implementations) won't be updated to preserve your changes. API interface files are always updated regardless of this setting. --- # Introduction ## What is API Monitoring?[​](#what-is-api-monitoring "Direct link to What is API Monitoring?") API Monitoring is a feature that allows you to monitor the usage of your API. It allows you to detect anomalies and to see how your API is used during runtime. This is especially useful during development and testing. ## How does it work?[​](#how-does-it-work "Direct link to How does it work?") API Monitoring is a protocol that is implemented by the ApiGear templates. It allows clients to send API usage information to an API Monitoring Service. For development a server is embedded into your local ApiGear Studio / CLI application. This allows developers to see in real-time how the API is used and detect any anomalies fast. ## How to use it?[​](#how-to-use-it "Direct link to How to use it?") To use API Monitoring you need to use a template that supports it. Please check the documentation of the template you are using for more information. When you generate your API SDK you can use the `monitor` feature to include the monitoring code into your API SDK. This will allow you to send API usage information to the monitoring server. When your client application is running messages will be send to the monitoring server. You can then use the monitoring server to see the API usage information. ## How to setup the monitoring server?[​](#how-to-setup-the-monitoring-server "Direct link to How to setup the monitoring server?") The monitoring server is embedded into the ApiGear Studio / CLI application. You can find the monitoring server settings in the settings page of the ApiGear Studio. note If your template supports tracing, please consult the individual template documentation. --- # Monitoring Protocol The API Monitor protocol is a simple HTTP post call to a dedicated http server instance embedded in the ApiGear Studio / CLI. The protocol allows to send property changes, operation calls and signals to the monitoring server. The message is typically a JSON message send to an HTTP endpoint depending if an operation call, state change or a signal is reported. note An extended version of the monitoring protocol is planned to provide detailed API analytics information our upcoming cloud API analytics solution for devices. ## HTTP Tracing[​](#http-tracing "Direct link to HTTP Tracing") The http tracing endpoint can be looked up under ApiGear Studio settings page. All HTTP traces work in batch mode. You need to send an array of traces to the server. The oldest trace should be the first element in the array. This should normally be the order you recieve the traces from a FIFO queue. In case no ID was transferred we generate a running ID based on the current timestamp. ### Tracing operation calls[​](#tracing-operation-calls "Direct link to Tracing operation calls") For an method call the message looks like this: An API call occurs when the client calls an method. The uri is the module name, joined with the interface name. A URI fragment (`#`) is added for the interface methods. ``` { "type": "call", "symbol": "${module}.${interface}/${method}", "data": "${params}" } ``` ### Tracing property changes[​](#tracing-property-changes "Direct link to Tracing property changes") A state change can be an partial update or a full update of all interface properties. The state is always an JSON object. ``` { "type": "state", "symbol": "${module}.${interface}", "data": "${state}" } ``` ### Reporting a signal notification[​](#reporting-a-signal-notification "Direct link to Reporting a signal notification") For an interface signal the message looks like this ``` { "type": "signal", "symbol": "${module}.${interface}/${signal}", "data": "${params}" } ``` ## Websocket Tracing[​](#websocket-tracing "Direct link to Websocket Tracing") Tracing over web sockets uses the JSON-RPC protocol. The protocol is mostly the same as the http protocol. The JSON RPC method is called `trace` and uses the same endpoint as the simulation server for ApiGear Studio. Where in the HTTP trace protocol the source as part of the endpoint, in the websocket protocol it is part of the message. Otherwise all parameters, especially also the `type` are the same. The websocket tracing endpoint can be looked up under ApiGear Studio settings page. ``` { "jsonrpc": "2.0", "method": "trace", "params": { "id": "1000230240", "source": "local-device", "type": "call", "symbol": "count.Counter/increment", "data": {} } } ``` --- # Advanced Concepts Advanced ObjectAPI concepts. ## Document information[​](#document-information "Direct link to Document information") The info section allows user to add information related to the current document. ``` info: license: ``` ## Meta information[​](#meta-information "Direct link to Meta information") Sometimes it is required to add additional information, which is not part of the ObjectAPI specification. For this the meta tag can be used. ``` interfaces: - name: Tuner meta: singleton: true config: { port: 1024 } ``` As the information is not part of the specification the applied code generator must have an understanding of the data. For example a C++ code generator could create a singleton type from the interface declaration. ## Compact Writing[​](#compact-writing "Direct link to Compact Writing") YAML allows a compact format for single line information. This allows us to shorten API definitions considerable. For example this API ``` structs: - name: Error fields: - name: message type: string - name: code type: int ``` Can be written in a short format like this: ``` structs: - name: Error fields: - { name: message, type: string } - { name: code, type: int } ``` --- # API Foundation ObjectAPI is defined in terms of modules inside a system. Each module is described as an ObjectAPI document. ObjectAPI is a YAML based specification to describe objects as interfaces. ## Format[​](#format "Direct link to Format") ObjectAPI is described as an open specification format using JSON Schema. Every ObjectAPI document must conform to this JSON schema definition. tip While the the API is described in JSON, other formats like YAML can be used as input formats and are automatically converted to JSON by the ObjectAPI tooling. Unless otherwise noted all file names in this specification are case sensitive. ## Managing Documents[​](#managing-documents "Direct link to Managing Documents") The ObjectAPI documents are simple files on the file system. Several files can be processed together and form a system. It is convention that the file name matches the module name. * `*.module.yaml` | `*.module.json` - ObjectAPI document Other files can be added which contain meta information for the API modules. They allow to inject additional information which is not relevant or available during API definitions. * `*.module.meta.yaml` | `*.module.meta.json` - ObjectAPI meta information injected into the relevant APIs. So if a module is named `org.example` the ObjectAPI document should be called `org.example.module.yaml` tip Outside of the ObjectAPI specification a solution document format is described which binds several modules together and links them to a code template for code generation. ## Data Types[​](#data-types "Direct link to Data Types") In the ObjectAPI specification data types are used in many locations. State, Method return types and parameters, signal parameters or structures. Data fields are added at the same level to describe the data name and type. For example for the interface properties, these are: ``` properties: - name: count type: int ``` The general types available to ObjectAPI are: * Primitive Types: `bool`, `int`, `int32`, `int64`, `float`, `float32`, `float64`, `string` * Container Types: `arrays` * Complex Types : `structures`, `enumerations` ## Primitives[​](#primitives "Direct link to Primitives") Data types can be represented in different forms in different programming languages. They all need to be convertible to JSON data types on request. * `bool` - A simple boolean value (true or false) * `int`, `int32`, `int64` - A signed integer value * `float`, `float32`, `float64` - A floating point value * `string` - A string value ## Arrays[​](#arrays "Direct link to Arrays") An array is an index based list of primitive or complex data types. Further nesting of containers are not supported but can be achieved using structs as array items. A data type is converted into a container by setting the type to array and specifying the containing type in the items key. For example an integer array can be noted like this: ``` properties: - name: names type: string array: true ``` If an array does contain a symbol as containing type, then the symbol name can be used in the items key. ``` properties: - name: messages type: Message array: true ``` Primitive types always start with a lower case character and symbols always with an upper case character. tip The code templates will change the casing based on the target language preferences. ## Complex Types[​](#complex-types "Direct link to Complex Types") A symbol is a named element inside a module. This can be either an interface, struct or enum/flag symbol. ``` structs: - name: Message ``` Inside the same module the type can be referenced by the name of the symbol using a `ref`. This holds true for all symbols. ``` properties: - name: msg1 type: Message - name: msg2 type: Message array: true ``` Outside the module, the module itself needs to be imported and the type needs to be used with its fully qualified name ``` imports: - org.example interfaces: - name: Interface1 properties: - name: msg1 type: org.example.Message ``` * `org.example.Message` - external symbol Note: Not every language profile does support importing. ## Rich Text Formatting[​](#rich-text-formatting "Direct link to Rich Text Formatting") Throughout the specification `description` support the markdown syntax. --- # API Examples ## Counter[​](#counter "Direct link to Counter") The typical counter example to increment and decrement a count value. ``` schema: apigear.module/1.0 name: org.example version: "1.0" info: title: "Counter API" description: A counter module to demonstrate basic interface patterns interfaces: - name: Counter description: Counter interface to count up and down properties: - name: count type: int operations: - name: increment - name: decrement ``` ## Radio Tuner[​](#radio-tuner "Direct link to Radio Tuner") A radio tuner with a current station and a station list. It can operate on different wavebands. ``` schema: apigear.module/1.0 name: entertainment.tuner version: "1.0" interfaces: - name: Tuner description: A tuner service to manage tuner stations properties: - name: currentStation type: Station description: current selected station - name: stationList type: Station array: true description: list of current available stations operations: - name: nextStation description: sets current station to next station from list - name: previousStation description: sets current station to previous station from list - name: updateCurrentStation description: update current station params: - name: stationId type: int structs: - name: Station fields: - name: stationId type: int description: station id - name: name type: string description: station name - name: modified type: string description: station last time modified enums: - name: State description: State for tuner interface members: - name: None description: tuner not initialized - name: Loading description: tuner is loading - name: Ready description: tuner is ready and operational - name: Error description: tuner received an error - name: Waveband members: - name: FM description: FM waveband - name: AM description: AM waveband ``` ## Weather Station[​](#weather-station "Direct link to Weather Station") The weather station uses a more compact YAML format to display the current temperature. ``` schema: apigear.module/1.0 name: sensors.weatherstation version: "2.5" interfaces: - name: WeatherStation properties: - { name: temperature, type: float } - { name: lastError, type: Error } operations: - { name: reset } signals: - name: error params: - { name: error, type: Error } structs: - name: Error fields: - name: msg type: string ``` --- # ObjectAPI IDL The ObjectAPI IDL is a **developer-friendly syntax** for defining APIs. It looks like a programming language, making it intuitive to read and write. ## IDL vs YAML[​](#idl-vs-yaml "Direct link to IDL vs YAML") | Aspect | IDL Format | YAML Format | | ------------------ | --------------------- | ----------------------- | | **Syntax** | Code-like, concise | Structured, explicit | | **Best for** | Authoring by hand | Programmatic generation | | **File extension** | `.idl` | `.module.yaml` | | **Tooling** | Auto-converts to YAML | Canonical format | Both formats are **fully equivalent** — the IDL is automatically transformed to YAML internally. Use whichever you prefer. ``` module demo 1.0 // Counter is a simple counter object interface Counter { // count is the current counter value count: int // increment the count property by step increment(int step) // decrement the count property by step decrement(int step) } ``` ## Interfaces[​](#interfaces "Direct link to Interfaces") An interface defines a set of properties, operations and signals. ``` interface name { `prop-name` : type // property `method-name` ( params ): `type` // operation signal `signal-name` ( params ) // signal } ``` A type can be either a primitive type or a complex type. A complex type can be a structure, enumeration or another interface. A primitive type can be one of the following: * `bool` : boolean value * `int`, `int32`, `int64` : integer value * `float`, `float32`, `float64` : floating point value * `string` : string value A complex type can be one of the following: * name of an interface * name of an enumeration * name of a structure To define arrays of a type just append the `[]` to the type. ``` struct Struct1 {} interface Demo { propString: string propStringArray: string[] propStruct: Struct1 } ``` tip Currently we do not support importing other modules. This is planned for a future version. ## Structures[​](#structures "Direct link to Structures") A data structure is a message which can be used to send complex information between objects. A structure is defined as follows: ``` struct name { fieldName : type } ``` ## Enumerations[​](#enumerations "Direct link to Enumerations") An enumeration is a set of named values. An enumeration is defined as follows: ``` enum Enum1 { Value1 = 0 Value2 = 1 Value3 = 2 } ``` A value can also be a hex number in the format of `0x2`. If there is no value specified the next value is used. ## Example[​](#example "Direct link to Example") Here is a complete example: ``` module demo 1.0 interface Radio { // frequency is the current frequency in MHz frequency: float // stations is a list of stations stations: Station[] // if enabled automatically tunes to the next station autoTune: bool // scan for stations scan() // tune to a specific frequency tuneFrequency(float frequency) // tune to a specific station tuneStation(stationId: int) // signal emitted when a new station is found signal stationFound(stationId: int) } struct Station { // id is the station id id: int // name of the station name: string // short name of the station shortName: string // frequency in MHz frequency: float // artwork url artwork: string // category of the content category: Category } enum Category { News = 0 Music = 1 Talk = 2 } ``` Our ApiGear tooling can create code in different languages. For example in C++ this would look like (simplified): ``` class Radio { public: // scan for stations void scan(); // tune to a specific frequency void tuneFrequency(float frequency); // tune to a specific station void tuneStation(int stationId); // signal emitted when a new station is found void onStationFound(function callback); public: // frequency is the current frequency in MHz float frequency; // stations is a list of stations std::vector stations; // if enabled automatically tunes to the next station bool autoTune; }; struct Station { // id is the station id int id; // name of the station std::string name; // short name of the station std::string shortName; // frequency in MHz float frequency; // artwork url std::string artwork; // category of the content Category category; }; ``` --- # Introduction ObjectAPI is a specification for defining **stateful, object-oriented APIs**. Unlike REST or RPC specifications that model stateless request/response interactions, ObjectAPI models interfaces as objects with observable state. ## A Shared Language, From Prototype to Production[​](#a-shared-language-from-prototype-to-production "Direct link to A Shared Language, From Prototype to Production") ObjectAPI deliberately models the **familiar object pattern** — **properties**, **operations** (methods), and **signals** (events) — so a single definition reads naturally to everyone who touches it. That makes it a **common ground** where designers and backend engineers align on one contract, instead of trading documents and assumptions. And because the same definition drives simulation, generated stubs, and production SDKs, a single ObjectAPI artifact carries a feature **from rapid prototype to production** — no throwaway spec, no rewrite when you cross into real code. ## Why Stateful APIs?[​](#why-stateful-apis "Direct link to Why Stateful APIs?") Most API specifications — OpenAPI, and gRPC/protobuf in their common request/response form — are built around stateless request/response. But many real-world systems have inherent state: | Domain | Examples of State | | ------------------- | ------------------------------------------------- | | **Automotive** | Vehicle speed, door lock status, climate settings | | **Game Engines** | Player position, inventory, game state | | **IoT/Embedded** | Sensor readings, device configuration | | **UI Applications** | Form data, selection state, view models | ObjectAPI models APIs the way programmers naturally think about objects — with **properties** (state), **operations** (methods), and **signals** (events). ## The Three Pillars[​](#the-three-pillars "Direct link to The Three Pillars") | Concept | What it is | Analogy | | -------------- | ------------------------------------------ | ------------------------------------------------ | | **Properties** | Observable state that can change over time | Class member variables with change notifications | | **Operations** | Methods you can call (sync or async) | Class methods | | **Signals** | Server-initiated events pushed to clients | Qt signals, C# events, callbacks | ## Two Formats, One Specification[​](#two-formats-one-specification "Direct link to Two Formats, One Specification") ObjectAPI supports two equivalent formats: ### IDL Format (recommended for authoring)[​](#idl-format-recommended-for-authoring "Direct link to IDL Format (recommended for authoring)") A concise, developer-friendly syntax that looks like a programming language: ``` module org.example 1.0 interface Thermostat { // Properties (state) temperature: float // current temperature targetTemp: float // desired temperature isHeating: bool // heating active? // Operations (methods) setTarget(float temp) reset() // Signals (events) signal overheated(float temp) } ``` ### YAML Format (canonical)[​](#yaml-format-canonical "Direct link to YAML Format (canonical)") The same API in YAML — used internally and for programmatic generation: ``` schema: apigear.module/1.0 name: org.example version: "1.0" interfaces: - name: Thermostat properties: - { name: temperature, type: float } - { name: targetTemp, type: float } - { name: isHeating, type: bool } operations: - name: setTarget params: - { name: temp, type: float } - name: reset signals: - name: overheated params: - { name: temp, type: float } ``` The IDL format is automatically transformed to YAML by ApiGear tools. Both formats are fully equivalent. ## When to Use ObjectAPI[​](#when-to-use-objectapi "Direct link to When to Use ObjectAPI") **Use ObjectAPI when:** * Your system has observable state that changes over time * Clients need to be notified of state changes (not just poll) * You're building interfaces for C++, Qt, Unreal, or embedded systems * You want APIs that map naturally to object-oriented code **Consider alternatives when:** * You're building stateless REST APIs (use OpenAPI) * You need high-performance binary serialization only (use protobuf) * You're documenting HTTP endpoints (use OpenAPI) ## Specification Details[​](#specification-details "Direct link to Specification Details") #### Version 0.2.0[​](#version-020 "Direct link to Version 0.2.0") The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](http://www.ietf.org/rfc/rfc2119.txt). ## Definitions[​](#definitions "Direct link to Definitions") | Term | Definition | | --------------- | ---------------------------------------------------------------------------------- | | **System** | A collection of modules describing a coherent set of APIs | | **Module** | A namespaced collection of interfaces, structures, and enumerations (one per file) | | **Interface** | A named object with properties, operations, and signals | | **Property** | Observable state on an interface that can change and notify observers | | **Operation** | A method that can be called on an interface | | **Signal** | An event emitted by the server to notify clients | | **Structure** | A data type with fields (no operations or signals) | | **Enumeration** | A set of named integer values | --- # API Modules ## Module Namespaces[​](#module-namespaces "Direct link to Module Namespaces") A module is a namespace for ObjectAPI symbols like interfaces, structures and enumerations. The module bundles these symbols together in one namespace. A module is identified by its `name` and `version`. The module name should be typically lowercase and words separated by `.`, like a reverse URI notation `org.example`. A module can have an additional `info` block to describe in more detail the module information. Typically a module consist of the `apigear.interfaces` version declaration, the module name and version, the list of interfaces, structures and signals. The version number must be written as a string, otherwise it wil be converted to a numeric value (e.g. 1 for 1.0). Only ObjectAPI, name, version are mandatory. The other identifiers are optional. ``` schema: apigear.module/1.0 name: org.example version: "1.0" interfaces: structs: enums: ``` ## Interfaces[​](#interfaces "Direct link to Interfaces") An interface is the main instance to describe your software boundary using interface terms. The interface consist of state, operation and signals. The state is typically describe a a set of properties of the interface and operations modify the interface state. Signals notify the user of changes of the interface. The interface itself is identified by its `name` inside a module. ``` schema: apigear.module/1.0 name: "org.example" version: "1.0" interfaces: - name: MyInterface ``` ### Object state[​](#object-state "Direct link to Object state") Each property has a `name` and a `type` as also `description` and additional `meta` data. ``` # ... interfaces: - name: MyInterface properties: - name: value type: int ``` ### Operation[​](#operation "Direct link to Operation") A operation defines the interaction with the interface. It is a collection of operations which can either manipulate the properties or return data. Ideally you design your operations in a way that they can be divided into commands and queries. A command is an operation which does something on the interface and a query collects data from the interface and returns it to the user. ``` # ... interfaces: - name: MyInterface operations: - name: command description: A command does not have a return type - name: query type: string description: A query returns data ``` Operation can have parameter arguments ``` # ... interfaces: - name: MyInterface operations: - name: command params: - name: step type: int return: type: int ``` The arguments do parameterize the operation. ### Signals[​](#signals "Direct link to Signals") A signal allows the interface to notify the outside world about events happening, e.g. triggered by others. A signal is like an operation, but never defines a type. ``` # ... interfaces: - name: MyInterface signals: - name: error params: - name: code type: int ``` ## Data Structures[​](#data-structures "Direct link to Data Structures") A structure represents a data structure which can be used for communication. The structure consists of a `name` and a set of data fields. Each field again has a `name` and a `type` information. ``` # ... structs: - name: Message fields: - name: msg type: string ``` A data structure does not contain any operations or signals. A data structure is typically used as a type for properties, operation parameters and others. ``` # ... interfaces: - name: MessageSender properties: - name: lastMessage type: Message operations: - name: send params: - name: msg type: Message ``` A data structure is identified by its name. Data structures can be nested by using the type name of the nested type. In some programming languages care needs to be taken by the order of declaration. ## Enumerations[​](#enumerations "Direct link to Enumerations") Enumerations and Flags are value types, which allow a user to use a defined number of choices to identify a value. ``` enums: - name: Status members: - name: None value: 0 - name: Loading value: 1 - name: Ready value: 2 - name: Error value: 3 ``` The values are optional and when missing the value is counted incrementally from 0 on upwards. ``` enums: - name: Status members: - name: None - name: Loading - name: Ready - name: Error ``` An enumeration is also a symbol and can be used by its name to identify its type. ``` # ... interfaces: - name: MyInterface properties: - name: status type: Status # references the Status enumeration ``` --- # HTTP Mapping ObjectAPI binds to HTTP using an RPC model. We find that describiung an API using an RPC model is much simpler for users than thinking about REST resources. ObjectAPI consist of modules, interfaces and properties, operations and signals. Due to the nature of HTTP being a uni-directional protocol,we can not simply model the bid-directional nature of an object communication over to HTTP. Especially the property updates and signals are difficult to map to HTTP actions. Experimental The **HTTP** mapping is experimental and not all templates support this feature. ## ObjectAPI Mapping[​](#objectapi-mapping "Direct link to ObjectAPI Mapping") * **module**: Is a namespace and does not map directly. * **interface**: `/${module}/${interface}` * **GET** fetches the state of the interface * **POST** sets the interface state * **PATCH** partial update of the interface state * **operation**: `/${module}/${interface}/${operation}` * **POST** invokes the operation and returns the response Structure are mapped like the normal JSON mapping of ObjectAPI data types. Same applies to enumerations. --- # Protocol Mappings ObjectAPI describes object communication patterns based on simple to use protocols. These communication patterns can be mapped to other communication patterns. ## API Types[​](#api-types "Direct link to API Types") There exists currently several API types, like REST, Message Based or RPC. ObjectAPI supports a mixture of these. | API style | Communication | State model | Real-time updates | Strengths | Trade-offs | | ----------------- | --------------------------------- | ------------------------------ | ----------------- | ------------------------------------------------------------------ | ------------------------------------------------ | | **REST** | request/response over HTTP | stateless; client drives logic | none (poll) | ubiquitous tooling, cacheable, simple | chatty for live state; logic leaks to the client | | **RPC** | request/response (function calls) | stateless calls | none | natural call semantics, efficient | tighter coupling; no built-in events | | **Message based** | publish/subscribe via broker | event / stream | yes (push) | decoupled, many-to-many, real-time | needs a broker; eventual consistency | | **ObjectAPI** | properties, methods & signals | **stateful objects** | yes (auto-synced) | models real state, clean developer API, maps onto all of the above | needs a code generator (ApiGear) | ### REST based APIs[​](#rest-based-apis "Direct link to REST based APIs") REST API is about browsing data but the underlying nature of the protocol is HTTP. HTTP is a request/response protocol and as such is architecture wise next to RPC. REST itself defines an architectural style on top of HTTP. For example to increment a value this logic would be in REST like this. We first fetch the counter state, than increment the count value and push back the result. The logic is on the client side and the service mostly manages data. A typical API would look like this: ``` const client = new HttpClient(); const data = await client.get("/counter/$id"); data.count += 1; await client.put("/counter/$id"); ``` Often these kind of APIs makes it hard in complex logic driven services to validate operations and data. ### Message based APIs[​](#message-based-apis "Direct link to Message based APIs") Message based APIs are typically realized using a message broker. The broker is responsible to ensure all messages are delivered to the subscribed or registered peers. First we subscribe to an interface's state changes. Then we publish an increment message and wait for changes on the interface state. The changes are announced by the service via the broker. A typical message based client would look like this: ``` const client = new MessageClient(); client.subscribe("/counter/$id"); client.on("/counter/$id", (v) => { console.log(v); }); client.publish("/counter/$id/increment"); ``` ### Object based APIs[​](#object-based-apis "Direct link to Object based APIs") Object based APIs focus on the developer API and take care of the internal mapping to the different protocol types. Interface properties will be typically automatically synced and signals will allow service side notifications to the clients. The API for this would look like this. ``` const client = new CounterClient(); client.on((s) => { console.log(s.count); }); await client.increment(); ``` First we register a callback when the interface state changes. Then we call the operation, as we defined an object API the API feels and works as developers would expect this. This makes it much nicer and easier to use the API inside your application. The API pattern also extends to the service side, where service calls land in an API that looks very much like the defined ObjectAPI. ## Is ApiGear an alternative to REST, gRPC, AsyncAPI, or DDS?[​](#is-apigear-an-alternative-to-rest-grpc-asyncapi-or-dds "Direct link to Is ApiGear an alternative to REST, gRPC, AsyncAPI, or DDS?") Short answer: **no — and that's the point.** ApiGear works at a different layer. You describe your interface once with ObjectAPI (the *spec*), and ApiGear *generates* the client and service code that runs **over** a transport like OLink, MQTT, NATS, or HTTP. REST, gRPC and MQTT are things ApiGear targets, not things it replaces. It helps to separate the layers: | Layer | What it is | Examples | | -------------- | ------------------------ | ------------------------------------------------------------------------------------ | | **Spec / IDL** | describes the interface | **ObjectAPI**, OpenAPI, AsyncAPI, Protobuf | | **Paradigm** | the interaction model | stateful objects (ObjectAPI) · request/response (REST, RPC) · pub/sub (messaging) | | **Transport** | moves the bytes | OLink, MQTT, NATS, HTTP, HTTP/2 — plus service/data middlewares like SOME/IP and DDS | | **Codegen** | turns the spec into SDKs | **ApiGear**, OpenAPI Generator, protoc | ApiGear spans the spec, paradigm and codegen layers while staying transport-agnostic, so the same definition can run over different transports — chosen per feature. **vs OpenAPI** — Both are specs you generate code from. OpenAPI describes *request/response HTTP APIs* (the resource-oriented, typically stateless REST style); ObjectAPI describes *stateful objects* — observable properties, operations, and server-pushed signals — with a single source of truth. If your service is mostly CRUD over HTTP, OpenAPI is a fine fit. If it has live state that clients must stay in sync with, ObjectAPI models that directly. **vs AsyncAPI** — AsyncAPI is the event-driven counterpart to OpenAPI: a spec for *messages and channels* over messaging technologies like MQTT, Kafka or NATS. It describes the *messaging* — you still design topics and payloads. ObjectAPI describes the *object* — properties, operations, signals — and generates the messaging for you (properties auto-sync, signals become events). Reach for AsyncAPI when the message stream itself is the contract; reach for ObjectAPI when stateful objects are, and you'd rather not hand-design every topic. **vs gRPC** — gRPC is an RPC *framework*: Protobuf (its IDL) + HTTP/2 (its transport) + streaming + codegen, bundled together. ApiGear sits one layer up — you define the object model once and generate code over whichever transport you choose. gRPC isn't a built-in transport today, but the templates are extensible: a gRPC binding *can* be added — generating gRPC services *from* your ObjectAPI through the same template extension point the built-in OLink, MQTT and NATS bindings use. The honest framing is "generate gRPC **with** ApiGear," not "ApiGear **or** gRPC." **vs MQTT / NATS** — These are *transports*, not API definitions. ApiGear already generates code that speaks them. You keep your broker; ApiGear gives you a typed object API on top instead of hand-written topic strings and payload parsing. **vs SOME/IP & DDS (automotive & embedded middleware)** — These are service and data middlewares — transports with a built-in service model, not API generators. The fit is unusually close: a SOME/IP **field** (getter + setter + notifier) is essentially an ObjectAPI **property**, and its methods and events map to operations and signals; DDS's data-centric publish/subscribe maps to property sync. Neither is a built-in transport today, but the object model lines up cleanly — so the same template extension point used for the built-in transports could generate SOME/IP or DDS bindings *from* one ObjectAPI definition, as it could for gRPC. You keep your middleware; ApiGear gives you one typed object model shared across every ECU and language. ### When ApiGear is the wrong tool[​](#when-apigear-is-the-wrong-tool "Direct link to When ApiGear is the wrong tool") * A simple, public, cacheable CRUD API → plain REST/OpenAPI is lighter. * A one-off script with no shared state → you don't need generated stubs. * Your team is standardized on one middleware's own IDL and tooling (e.g. gRPC or DDS) and doesn't need multi-language generation → use it directly (or add a template if you want ObjectAPI's object model on top). ## Choosing a transport[​](#choosing-a-transport "Direct link to Choosing a transport") ObjectAPI generates the same interface for several transports (also called IPC implementations). Pick the one that fits your topology — each links to its full wire mapping: | Transport | Pattern | Needs | Real-time push | Late-join state | Best for | ApiGear Simulation | | ------------------------------------------------ | ------------------------------------ | -------------- | ------------------- | ----------------------- | ----------------------------------------- | ------------------ | | **[OLink](/docs/protocols/objectlink/intro.md)** | point-to-point live link (WebSocket) | a server URL | yes (live) | live link only | tight client↔service and simulation links | ✅ | | **[MQTT](/docs/protocols/mqtt/intro.md)** | publish/subscribe via broker | an MQTT broker | yes | retained messages | IoT, telemetry, many-to-many | — | | **[NATS](/docs/protocols/nats/intro.md)** | publish/subscribe and request/reply | a NATS server | yes | `init` / `state` resync | high-throughput cloud and edge messaging | — | | **[HTTP](/docs/protocols/http/mapping_http.md)** | request/response | a web server | no (request-driven) | n/a | simple, REST-style integration | — | note The [Unreal Engine template](/template-unreal/docs/features/msgbus.md) also ships a **Message Bus** transport (zero-configuration UDP for Unreal-to-Unreal IPC), which is specific to Unreal and not part of the cross-language set above. --- # ApiGear over MQTT [MQTT](https://mqtt.org/) is a lightweight publish/subscribe messaging protocol widely used for IoT, telemetry, and many-to-many communication. ApiGear generates **client** and **service adapter** code that exposes your [ObjectAPI](/docs/objectapi/intro.md) interfaces over an MQTT broker, so an interface's **properties**, **signals**, and **operations** flow between any MQTT-speaking peers — C++, Qt, Python, Rust, Unreal Engine, web dashboards, or embedded devices. This page describes the **wire protocol**: how an interface maps onto MQTT topics and payloads. It is the same across every template, so a service generated for one language interoperates with a client generated for another. For the language-specific generated classes and usage, see the per-template pages linked below. A broker is required Unlike [OLink](/docs/protocols/objectlink/intro.md) (point-to-point), MQTT routes all traffic through an external broker (Mosquitto, EMQX, HiveMQ, AWS IoT Core, …). The broker decouples publishers from subscribers and handles message routing and retention. ## How it works[​](#how-it-works "Direct link to How it works") * A **service adapter** wraps your local interface implementation and publishes property changes, signals, and operation replies to the broker; it subscribes to inbound set-requests and operation calls. * A **client** implements the same interface but, instead of running the logic locally, publishes/subscribes to the broker — acting as a remote proxy. Properties are cached locally and updated from retained messages. * ApiGear uses **MQTT v5** so that operation calls can carry `CorrelationData` and `ResponseTopic` as message properties. A **v3.1.1 fallback** is available that embeds the same data in the payload (see the mapping page). ## In this section[​](#in-this-section "Direct link to In this section") * **[Topic Mapping](/docs/protocols/mqtt/mapping.md)** — the authoritative spec: topic structure, payloads, QoS, and the v3 fallback. * **[ApiGear MQTT Spec v0.1 (PDF)](/assets/files/ApiGearMQTTv0.1-b44583aa1c3fc1e5416afe55dd6470db.pdf)** — the formal specification document. * Protocol reference: [MQTT v5.0 (OASIS)](https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html). ## Implemented by[​](#implemented-by "Direct link to Implemented by") The MQTT transport is generated by these templates: * [C++17](/template-cpp17/docs/features/mqtt.md) · [C++14](/template-cpp14/docs/features/mqtt.md) · [Qt6](/template-qt6/docs/features/mqtt.md) · [Python](/template-python/docs/features/mqtt.md) · [Rust](/template-rust/docs/features/mqtt.md) · [Unreal Engine](/template-unreal/docs/features/mqtt.md) --- # ApiGear MQTT Topic Mapping This is the wire specification for running [ObjectAPI](/docs/objectapi/intro.md) interfaces over **MQTT v5**. It defines how an interface's members map to MQTT topics and payloads. The mapping is transport-level and language-independent — a service generated by any [template](/docs/sdk/intro.md) interoperates with a client generated by any other. > Formal document: **[ApiGear MQTT Spec v0.1 (PDF)](/assets/files/ApiGearMQTTv0.1-b44583aa1c3fc1e5416afe55dd6470db.pdf)**. ## Topic structure[​](#topic-structure "Direct link to Topic structure") All topics are built from the module name, interface name, a **verb**, and the member name, separated by `/`: ``` {moduleName}/{interfaceName}/{verb}/{memberName} ``` | Verb | Member | Direction | Purpose | | ------ | --------- | ---------------- | ------------------------------------------ | | `rpc` | operation | client → service | Invoke an operation (request) | | `sig` | signal | service → client | Signal broadcast | | `prop` | property | service → client | Property value notification (**retained**) | | `set` | property | client → service | Property change request | Using an interface `Hello` in module `io.world` as an example: | Direction | Topic | Purpose | | ---------------- | ------------------------------------------ | --------------------------------------- | | Client → Service | `io.world/Hello/set/last` | Set property request | | Service → Client | `io.world/Hello/prop/last` | Property change notification (retained) | | Client → Service | `io.world/Hello/rpc/say` | Operation invocation | | Service → Client | `io.world/Hello/rpc/say/{clientId}/result` | Operation reply (per-client) | | Service → Client | `io.world/Hello/sig/justSaid` | Signal broadcast | ## Operations (request / response)[​](#operations-request--response "Direct link to Operations (request / response)") Operations use a request topic and a per-client response topic. **Request** — the client publishes; the service subscribes: ``` Topic: {moduleName}/{interfaceName}/rpc/{functionName} Content: JSON array of the operation arguments, e.g. [42] MQTT v5: CorrelationData = unique call id (UUID); ResponseTopic = the reply topic below ``` **Response** — the service publishes to the caller's `ResponseTopic`; that client subscribes: ``` Topic: {moduleName}/{interfaceName}/rpc/{functionName}/{UniqueClientID}/result Content: JSON-encoded return value MQTT v5: CorrelationData = the same call id, echoed back unchanged ``` The `CorrelationData` property carries the unique call id as opaque bytes, which the service echoes back on the reply. This lets multiple clients invoke the same operation concurrently without their replies colliding — each client matches the echoed id to the call it made. ### Internal (non-API) operations[​](#internal-non-api-operations "Direct link to Internal (non-API) operations") Operations not declared in the API (template-internal helpers) use the same `rpc` verb with an **underscore** prefix on the function name, so they never clash with generated operations: ``` {moduleName}/{interfaceName}/rpc/_{functionName} ``` ## Signals[​](#signals "Direct link to Signals") The service publishes; clients subscribe: ``` Topic: {moduleName}/{interfaceName}/sig/{signalName} Content: JSON array of the signal arguments ``` ## Properties[​](#properties "Direct link to Properties") Property handling is split into a **change request** and a **value notification**: **Set (change request)** — the client publishes a desired value; the service subscribes: ``` Topic: {moduleName}/{interfaceName}/set/{propertyName} Content: JSON-encoded property value ``` **Notify (value)** — the service publishes the current value; clients subscribe: ``` Topic: {moduleName}/{interfaceName}/prop/{propertyName} Content: JSON-encoded property value ``` Retained state Property value notifications are published with **retain = true**. A newly-subscribing client therefore receives the current value immediately on subscription — no separate handshake is needed. ## Payloads & QoS[​](#payloads--qos "Direct link to Payloads & QoS") * Payloads are **JSON-encoded**. Operation requests carry an **args-only** array (e.g. `[42]`); the call id rides on the MQTT v5 `CorrelationData` property rather than inside the payload. * Property values and signal argument arrays are plain JSON. * Subscribe/publish use **QoS 1** (at-least-once delivery) by default. ## MQTT v3.1.1 fallback[​](#mqtt-v311-fallback "Direct link to MQTT v3.1.1 fallback") MQTT 3.1.1 has no user properties, so the v3 mapping moves the version into the topic and embeds the correlation data and response topic **inside the payload**: ``` Request: {moduleName}/{interfaceName}/rpc/v3/{functionName} Content: Data { correlationData (call id) + ResponseTopic } Response: {moduleName}/{interfaceName}/rpc/v3/{functionName}/{UniqueClientID}/result Content: Data { correlationData (call id) } ``` Prefer **MQTT v5** where the broker supports it; it keeps payloads clean and uses native correlation. ## Implemented by[​](#implemented-by "Direct link to Implemented by") * [C++17](/template-cpp17/docs/features/mqtt.md) · [C++14](/template-cpp14/docs/features/mqtt.md) · [Qt6](/template-qt6/docs/features/mqtt.md) · [Python](/template-python/docs/features/mqtt.md) · [Rust](/template-rust/docs/features/mqtt.md) · [Unreal Engine](/template-unreal/docs/features/mqtt.md) Related: [ApiGear over NATS](/docs/protocols/nats/intro.md) · [ObjectLink](/docs/protocols/objectlink/intro.md) · [Protocol Mappings](/docs/protocols/mapping.md) --- # ApiGear over NATS [NATS](https://docs.nats.io/) is a high-performance messaging system for cloud and edge applications. ApiGear generates **client** and **service adapter** code that exposes your [ObjectAPI](/docs/objectapi/intro.md) interfaces over NATS, so an interface's **operations**, **signals**, and **properties** flow between any NATS-connected peers. This page describes the **wire protocol**: how an interface maps onto NATS subjects and payloads. It is the same across every template that supports NATS, so a service generated for one language interoperates with a client generated for another. For the language-specific generated classes and usage, see the per-template pages linked below. ## How it works[​](#how-it-works "Direct link to How it works") * A **service adapter** wraps your local implementation, subscribes to operation and property-change subjects, and publishes signals and property values. * A **client** implements the same interface but publishes/subscribes over NATS instead of running the logic locally, acting as a remote proxy. * Operations use NATS's native **request/reply** mechanism. NATS also adds **connectivity messaging** (`service.available`, `init`, `init.resp`) so clients can discover a service and synchronise initial state. ## In this section[​](#in-this-section "Direct link to In this section") * **[Subject Mapping](/docs/protocols/nats/mapping.md)** — the subject structure, payloads, and connectivity messaging. * Protocol reference: [NATS documentation](https://docs.nats.io/). ## Implemented by[​](#implemented-by "Direct link to Implemented by") The NATS transport is generated by these templates: * [C++17](/template-cpp17/docs/features/nats.md) · [Rust](/template-rust/docs/features/nats.md) --- # ApiGear NATS Subject Mapping This is the wire specification for running [ObjectAPI](/docs/objectapi/intro.md) interfaces over **NATS**. It defines how an interface's members map to NATS subjects and payloads. The mapping is transport-level and language-independent — a service generated by any [template](/docs/sdk/intro.md) interoperates with a client generated by any other. ## Subject structure[​](#subject-structure "Direct link to Subject structure") NATS subjects use `.` as the separator. They are built from the module name, interface name, a **verb**, and the member name: ``` {moduleName}.{interfaceName}.{verb}.{memberName} ``` | Verb | Member | Direction | Purpose | | ------ | --------- | ---------------- | ----------------------------------- | | `rpc` | operation | client → service | Invoke an operation (request/reply) | | `sig` | signal | service → client | Signal broadcast | | `set` | property | client → service | Property change request | | `prop` | property | service → client | Property value notification | The property verbs match the [MQTT mapping](/docs/protocols/mqtt/mapping.md): `set` carries a change request to the service, and `prop` carries the value notification back to clients. ## Operations (request / reply)[​](#operations-request--reply "Direct link to Operations (request / reply)") Operations use NATS's native request/reply. The client publishes a request; the service subscribes and replies on the request's reply subject: ``` Subject: {moduleName}.{interfaceName}.rpc.{functionName} Content: JSON array of the operation arguments (string-encoded) ``` ### Internal (non-API) operations[​](#internal-non-api-operations "Direct link to Internal (non-API) operations") Operations not declared in the API use the `rpc` verb with an **underscore** prefix on the function name: ``` {moduleName}.{interfaceName}.rpc._{functionName} ``` ## Signals[​](#signals "Direct link to Signals") The service publishes; clients subscribe: ``` Subject: {moduleName}.{interfaceName}.sig.{signalName} Content: JSON array of the signal arguments (string-encoded) ``` ## Properties[​](#properties "Direct link to Properties") **Change request** — the client publishes a desired value; the service subscribes: ``` Subject: {moduleName}.{interfaceName}.set.{propertyName} Content: JSON-encoded property value (string) ``` **Value notification** — the service publishes the current value; clients subscribe: ``` Subject: {moduleName}.{interfaceName}.prop.{propertyName} Content: JSON-encoded property value (string) ``` ## Connectivity messaging[​](#connectivity-messaging "Direct link to Connectivity messaging") NATS has no broker-side retained state, so ApiGear adds explicit messages for service discovery and initial state synchronisation: | Message | Subject | Content | Direction | | ----------------- | --------------------------------------------------- | --------------------------- | ---------------- | | Service available | `{moduleName}.{interfaceName}.service.available` | empty | service → client | | Init request | `{moduleName}.{interfaceName}.init` | `clientId` | client → service | | Init response | `{moduleName}.{interfaceName}.init.resp.{clientId}` | initial state (JSON object) | service → client | A client subscribes to `service.available`, then sends an `init` request carrying its `clientId`; the service replies on `init.resp.{clientId}` with the current property values so the client starts in sync. ## Payloads[​](#payloads "Direct link to Payloads") Payloads are **string-encoded JSON** (`nlohmann::json`). Operation and signal arguments are JSON arrays; property values and init responses are JSON elements/objects. ## Implemented by[​](#implemented-by "Direct link to Implemented by") * [C++17](/template-cpp17/docs/features/nats.md) · [Rust](/template-rust/docs/features/nats.md) Related: [ApiGear over MQTT](/docs/protocols/mqtt/intro.md) · [ObjectLink](/docs/protocols/objectlink/intro.md) · [Protocol Mappings](/docs/protocols/mapping.md) --- # Use Cases Object Link can be used to link the UI layer to the underlying service layer but also to link low-level things like a sensor to a high-level thing like a dashboard. Due to the combination of properties/operations and signals the protocol can be used to describe a wide range of use cases. It can be used during development using JSON and later switch to a binary protocol for production for faster communication. ## Services[​](#services "Direct link to Services") When designing embedded platforms, it is often necessary to provide a state full design. The state is inherent from the device (e.g. temperature, location, current radio station). These states are often used by multiple applications. The applications are often not aware of each other and the state is not synchronized. This leads to a lot of duplicated code and a lot of complexity. The protocol allows to share state between applications. ANd the state is automatically synced across applications and platforms. The applications can be written in different languages and can be running on different platforms. The protocol is designed to be used with the [ObjectAPI](/docs/objectapi/intro.md) specification. This allows to define a common API surface for the applications. Protocol Features Used: * **Property**: read/write/notify * **Operations**: request/response * **Signals**: notify ## Sensors[​](#sensors "Direct link to Sensors") A sensor can just use the property aspects of the protocol. The sensor can send property changes to the application. The application can then use the property changes to update the UI. For this the sensor does have to be linked to the remote object. Protocol Features Used: * **Property**: write ## Actuators[​](#actuators "Direct link to Actuators") An actuator can just use the property aspects of the protocol. The application can send property changes to the actuator. The actuator can then use the property changes to update the state of the actuator. For this the actuator has to be linked to the remote object to receive changes. Protocol Features Used: * **Property**: notify ## Remote Procedure Calls[​](#remote-procedure-calls "Direct link to Remote Procedure Calls") The protocol can be used to implement remote procedure calls. The protocol is designed to be used with the [ObjectAPI](/docs/objectapi/intro.md) specification. This allows to define a common API surface for the applications. The protocol can be used to implement a remote procedure call. The request is send to the service and the service replies with the result. Additional the service can notify the client about events using the signal aspect of the protocol. Protocol Features Used: * **Operations**: request/response ## Highly Interactive Services[​](#highly-interactive-services "Direct link to Highly Interactive Services") The protocol can be used to implement highly interactive services. For example for a whiteboard application the protocol can dynamically update the UI about the current location of the pen and it's state. Actions can be expressed as operations using the request/reply aspect of the protocol. The signals can be used to send a constant stream of events to the client. Protocol Features Used: * **Property**: read/write/notify * **Operations**: request/response * **Signals**: notify --- # Error Messages Error can be raised in several places. The error message is always send back to the client with the message type of the original message. The error message is a JSON array with the following format: ``` [ ERROR, MsgType, RequestID, ErrorCode ] ``` * `MsgType`: integer value of message type * `RequestID`: the request id of the original message * `ErrorCode`: a JSON string value with the error code ## Error Codes[​](#error-codes "Direct link to Error Codes") Error codes are string messages starting with `olink.error.` followed by a short description of the error. The following error codes are defined: * `olink.error.invalid_message`: the message is not a valid JSON array * `olink.error.invalid_message_type`: the message type is not a valid integer * `olink.error.no_such_module`: the module does not exist * `olink.error.no_such_object`: the object does not exist * `olink.error.no_such_property`: the property does not exist * `olink.error.no_such_operation`: the operation does not exist tip Currently it is not possible to add details to the error message. This will be added in a future version. ## Invalid Message[​](#invalid-message "Direct link to Invalid Message") The error message is send if the client sends an invalid message. ``` [ ERROR, 0, 0, "olink.error.invalid_message" ] ``` ## Invalid Message Type[​](#invalid-message-type "Direct link to Invalid Message Type") The error message is send if the client sends an invalid message type. ``` [ ERROR, 0, 0, "olink.error.invalid_message_type" ] ``` ## No Such Module[​](#no-such-module "Direct link to No Such Module") The error message is send if the client tries to link to a non existing module. ``` [ ERROR, LINK, 0, "olink.error.no_such.module" ] ``` This error can also be send during an unlink operation if the module is not linked. ``` [ ERROR, UNLINK, 0, "olink.error.no_such.module" ] ``` ## No Such Object[​](#no-such-object "Direct link to No Such Object") The error message is send if the client tries to link to a non existing object. ``` [ ERROR, LINK, 0, "olink.error.no_such.object" ] ``` This error can also be raised during an unlink operation if the object does not exists. ``` [ ERROR, UNLINK, 0, "olink.error.no_such.object" ] ``` ## No Such Property[​](#no-such-property "Direct link to No Such Property") The error message is send if the client tries to set a property on a non existing property. ``` [ ERROR, SET_PROPERTY, 0, "olink.error.no_such.property" ] ``` ## No Such Operation[​](#no-such-operation "Direct link to No Such Operation") The error message is send if the client tries to invoke a non existing operation. ``` [ ERROR, INVOKE, 0, "olink.error.no_such.operation" ] ``` --- # Introduction ObjectLink is an IPC protocol based on the idea to link a local object to a remote object a network connection. Object link allows users to communicate state full objects over a network connection. The protocol is designed to be used with the [ObjectAPI](/docs/objectapi/intro.md) specification. The design is based on several main concepts: * Properties are reactive and synchronized between the local and remote object * Operations allows a request/response communication between the local and remote object * Signals are used to notify the local object about events on the remote object * Modules are used to group objects and to define a namespace * The local and remove object a represented by an easy to use object API surface * The object API surface can be defined by an ObjectAPI document ## JSON Notation[​](#json-notation "Direct link to JSON Notation") Throughout the protocol description, the JSON notation is used. All data types must be valid JSON data types. For transport several protocols are supported, see list below: * [JSON](https://www.json.org/json-en.html) * [MsgPack](https://msgpack.org/index.html) * [CBOR](https://cbor.io/) The client and server needs to know their transport and encoding in advance. There is no protocol negotiation designed. This allows us to keep the protocol simple. ## Message Types[​](#message-types "Direct link to Message Types") * [Lifecycle](/docs/protocols/objectlink/lifecycle.md) * `--> LINK` - link the local object with a remote object * `<-- INIT` - initialized the local object with properties from the remote object * `--> UNLINK` - unlinks a local object from a remote object * [Properties](/docs/protocols/objectlink/properties.md) * `--> SET_PROPERTY` - send a property change to a remote object * `<-- PROPERTY_CHANGE` - sends property changes to all linked client objects * [Methods](/docs/protocols/objectlink/methods.md) * `--> INVOKE` - invoke a method on a remote object * `<-- INVOKE_REPLY` - reply of an remote invokation * [Signals](/docs/protocols/objectlink/signals.md) * `<-- SIGNAL` - send remote events back to all linked client objects * [Errors](/docs/protocols/objectlink/errors.md) * `<-- ERROR` - send an error back to the client with the msg type of the original message ## Message Formats[​](#message-formats "Direct link to Message Formats") | Direction | Message | Value | Format | | --------- | ----------------- | ----- | --------------------------------------- | | `->` | `LINK` | 10 | \[ MsgType, ObjectId ] | | `<-` | `INIT` | 11 | \[ MsgType, ObjectId, Dict ] | | `->` | `UNLINK` | 12 | \[ MsgType, ObjectId ] | | `->` | `SET_PROPERTY` | 20 | \[ MsgType, PropertyId, Value ] | | `<-` | `PROPERTY_CHANGE` | 21 | \[ MsgType, ObjectId, Value ] | | `->` | `INVOKE` | 30 | \[ MsgType, RequestID, MethodId, Args ] | | `<-` | `INVOKE_REPLY` | 31 | \[ MsgType, RequestID, Value ] | | `<-` | `SIGNAL` | 40 | \[ MsgType, SignalId, Args ] | | `<-` | `ERROR` | 50 | \[ MsgType, MsgType, RequestID, Error ] | * `MsgType`: integer value of message type * `ObjectId`: a string identifying the resource as module and object name (e.g. `"demo.Calc"`) * `Dict`: A JSON dictionary, e.g. `{ "count": 0}` * `Args`: A JSON array, e.g. `[ 1, 2 ]` * `PropertyId`: A ObjectID with a property path (e.g. `"demo.Calc/count"`) * `Value`: Any valid JSON value including JSON arrays or objects * `MethodId`: A ObjectID with a method path (e.g. `"demo.Calc/increment"`) * `RequestId`: A unique integer value identifying the request during the connection. Typically a value incremented by one on each request and starting by 1 and then reset to 1 by max value. * `SignalId`: A ObjectID with a signal path (e.g. `"demo.Calc/shutdown"`) * `Error`: A string describing the error --- # Lifecycle To use a remote object the object needs to be linked first using the object name. The `LINK` request will be answered with a `INIT` message to initially populate the local properties from the remote object and subscribe to property changes and signals. After the linking the local object is fully usable. A link message is send by the client to link to a remote object. ``` --> [ LINK, "org.demos.Echo"] ``` A link message is answered with an init message with the initial properties, or an error message, in case of failure. ``` <-- [ INIT, 'org.demos.Echo', { message: "hello" } ] ``` To release the resources on the server side the object can also be unlinked, this will also unsubscribe from property changes and signals. ``` --> [ UNLINK, "org.demos.Echo"] ``` ## Sequence Diagram[​](#sequence-diagram "Direct link to Sequence Diagram") To link a local object to a remote object we need to send a link message. --- # Method Invokation A remote invokation invokes asynchronously a method on a remote object and returns the result. ## ApiGear Object Model[​](#apigear-object-model "Direct link to ApiGear Object Model") To model methods in ApiGear you define an interface with operations. ``` name: org.demos interfaces: - name: Echo operations: - name: say params: - name: msg type: string type: string ``` The operations will be generated as methods of the object. This will look simplified like this. ``` // org.demos.js class Echo { async say(msg: string): string } const echo = new Echo() console.log(echo.say("hello")) $> hello ``` ## Protocol Flow[​](#protocol-flow "Direct link to Protocol Flow") To invoke remote method an method name and the method arguments must be specified. The reply can only have on value, which can have any complexity. The local object sends an `INVOKE` message to the remote object using a request id, the method name and method arguments. ``` --> [ INVOKE, 1, "org.demos.Echo/say", ["echo"]] ``` The remote object executes the method and returns the reply or an error message in case of failure. ``` <-- [ INVOKE_REPLY, 1, "echo"] ``` ## Sequence Diagram[​](#sequence-diagram "Direct link to Sequence Diagram") After an object is linked remote methods can be called. --- # Property Synchronization An object can contain a set of properties, which describe the data associated with the object. When a property changes, either locally or remote all linked objecta re notified about the changes and updated. This allows to distribute data across all linked objects easily and share state between local and remote objects. ## ApiGear Object Model[​](#apigear-object-model "Direct link to ApiGear Object Model") In ApiGear properties are described as properties inside an interface. ``` name: org.demos interfaces: - name: Echo properties: - name: message type: string ``` The resulting simplified typescript code could look like this. ``` // org.demos.js class Echo { message: string = "" } const echo = new Echo() echo.message = "foo" ``` ## Protocol Flow[​](#protocol-flow "Direct link to Protocol Flow") First the local object needs to be linked to a remote object. ``` --> [ LINK, "org.demos.Echo"] ``` Now the local object receive initial property list, which is automatically send after the link message. ``` <-- [ INIT, 'org.demos.Echo', { message: "hello" } ] ``` After the init message out local object is fully populated and all properties have valid values. When a property is changed on the local object, for example from "hello" to "foo", a `SET_PROPERTY` message is send. ``` --> [ SET_PROPERTY, "org.demos.Echo/message", "foo"] ``` The remote object will then set the property and notify all linked objects about the changes using a `PROPERTY_CHANGE` message, including the original sender. ``` <-- [ PROPERTY_CHANGE, "org.demos.Echo/message", "foo"] ``` ## Sequence Diagram[​](#sequence-diagram "Direct link to Sequence Diagram") After an object is linked propertie will be synced across all linked clients. --- # Server Side Signals Signals re used to notify the local object about changes on the remote object. ## ApiGear Object Model[​](#apigear-object-model "Direct link to ApiGear Object Model") Signals can be modeled using ApiGear as signals of an interface. ``` name: org.demos interfaces: - name: Echo signals: - name: shutdown params: - name: timeout type: int ``` The resulting code will look somehow like this and most often will require a lambda function to be used for the notification. ``` // org.demos.js class Echo { onShutdown(callback) } const echo = new Echo() echo.onShutdown( (timeout) => { console.log('timeout: ', timeout); }) ``` ## Protocol Flow[​](#protocol-flow "Direct link to Protocol Flow") To receive signals the local object needs to be linked to the remote object first. ``` --> [ LINK, "org.demos.Echo"] ``` Then the remote object can send at any time signals to the linked client objects and notify them on changes. ``` <-- [ SIGNAL, "org.demos.Echo/shutdown", [10]] ``` To stop receiving signals, just unlink the remote object. ``` --> [ UNLINK, "org.demos.Echo"] ``` ## Sequence Diagram[​](#sequence-diagram "Direct link to Sequence Diagram") After an object is linked server side signals will be send. --- # JavaScript API Most examples in the API documentation are based on an API definition for a counter example. ``` module demo interface Counter { count: int increment() reset() signal resetted() } ``` The examples demonstrate how to interact with the simulation server using the JavaScript API and a client based on the provided API definition. ### $createService[​](#createservice "Direct link to $createService") ``` $createService(name:string, properties: {}) ``` Creates a service with the given name and properties and returns the service object. If an service with the given name already exists, it is returned. ### $getService[​](#getservice "Direct link to $getService") ``` $getService(name:string) ``` Returns the service with the given name. If no service with the given name exists, it will be created. ### $quit[​](#quit "Direct link to $quit") ``` $quit() ``` Cleanly shuts down the simulation engine. This function: * Disconnects all active channels * Closes the simulation engine * Exits the simulation script This is useful for gracefully terminating long-running simulations or when specific conditions are met. ``` // Example: Stop simulation after a condition if (temperature > 100) { console.log("Overheating detected, stopping simulation"); $quit(); } ``` ## Service[​](#service "Direct link to Service") A service is a virtual entity that can be created and destroyed and it has state and behavior as well notifies changes. services are accessed through a protocol adapter that is implemented by the simulation server. Services can be created using the `$createService` method. Services support properties through a natural JavaScript API using proxies. Properties can be accessed and modified directly, and changes can be monitored using the `.on()` method. The following example creates a service with the given name and state and returns the service. If a service with the given name already exists, it is returned. ``` // creates a service with the given name and state const counter = $createService("counter", { count: 0 }); // Direct property access and modification counter.count = 10; console.log(counter.count); // 10 // Monitor property changes counter.on("count", function (value) { console.log("count changed", value); }); counter.count = 11; // prints "count changed 11" // Access the raw service object when needed console.log(counter.$.getProperties()); // { count: 11 } ``` ### Properties[​](#properties "Direct link to Properties") #### Direct Property Access[​](#direct-property-access "Direct link to Direct Property Access") Services use JavaScript proxies to provide natural property access: ``` // Get property value const value = service.propertyName; // Set property value service.propertyName = newValue; ``` #### service.on[​](#serviceon "Direct link to service.on") ``` service.on(name: string, callback: (value: any) => void) ``` Register a callback to be called when a property changes or a signal is emitted. Unregister the callback by calling the returned unsubscribe function. ``` const counter = $createService("counter", { count: 10 }); const unsubscribe = counter.on("count", function (value) { console.log("count changed", value); }); counter.count = 11; // prints "count changed 11" unsubscribe(); ``` #### Raw Service Access[​](#raw-service-access "Direct link to Raw Service Access") When you need access to the underlying service object, use the `$` property: ``` // Access raw service methods service.$.getProperties() // Returns all properties as an object service.$.setProperties({ prop1: value1, prop2: value2 }) // Set multiple properties service.$.hasProperty(name) // Check if property exists service.$.getProperty(name) // Get property value (alternative to direct access) service.$.setProperty(name, value) // Set property value (alternative to direct access) ``` ### Methods[​](#methods "Direct link to Methods") #### Natural Method Definition[​](#natural-method-definition "Direct link to Natural Method Definition") Methods are defined by assigning functions to service properties. The function automatically receives the service proxy as `this`: ``` const counter = $createService("counter", { count: 0 }); // Define a method with automatic 'this' binding counter.increment = function() { this.count++; // 'this' refers to the service proxy this.emit('incremented', this.count); // Can emit signals }; // Call the method counter.increment(); ``` #### Raw Method Access[​](#raw-method-access "Direct link to Raw Method Access") When needed, you can access methods through the raw service object: ``` // Check if method exists service.$.hasMethod("methodName") // Returns boolean // Get method reference service.$.getMethod("methodName") // Returns the function // Call method through raw API service.$.callMethod("methodName", arg1, arg2) // Calls with arguments ``` ### Signals[​](#signals "Direct link to Signals") #### service.emit[​](#serviceemit "Direct link to service.emit") ``` service.emit(name: string, ...args) ``` Emit a signal on the service. ``` const counter = $createService("counter", { count: 0 }); // Define a method that emits a signal counter.reset = function() { this.count = 0; this.emit('resetted'); // Emit signal with no arguments this.emit('stateChanged', this.count); // Emit with arguments }; ``` #### Listening to Signals[​](#listening-to-signals "Direct link to Listening to Signals") Use the same `on` method to listen for both property changes and signals: ``` const counter = $createService("counter", { count: 0 }); // Listen to custom signals const unsubscribe = counter.on("resetted", function () { console.log("Counter was reset"); }); counter.on("stateChanged", function (newValue) { console.log("State changed to:", newValue); }); // Trigger the signals counter.reset(); // prints "Counter was reset" // prints "State changed to: 0" unsubscribe(); // unregister the callback ``` #### Raw Signal Access[​](#raw-signal-access "Direct link to Raw Signal Access") For direct signal manipulation through the raw API: ``` service.$.onSignal(name, callback) // Register signal listener service.$.emitSignal(name, ...args) // Emit signal ``` ## Async Operations[​](#async-operations "Direct link to Async Operations") The simulation environment provides basic async support through: ### setTimeout[​](#settimeout "Direct link to setTimeout") ``` setTimeout(callback, delay) ``` Schedules a function to be called after a specified delay (in milliseconds). ``` // Example: Delayed state change setTimeout(function() { heater.temperature = 25; console.log("Temperature updated after delay"); }, 1000); // Execute after 1 second // Example: Sequential operations in vehicle example const interval = setInterval(function() { indicators[indicator] = true; console.log(`Turned on ${indicator}`); }, 200); ``` Note: The simulation environment currently supports `setTimeout` and `setInterval` but not `setImmediate`, `clearTimeout`, or `clearInterval`. ## Console Output[​](#console-output "Direct link to Console Output") Standard console methods are available for debugging and output: ``` console.log("Info message"); console.warn("Warning message"); console.error("Error message"); ``` All console output is integrated with the simulation server's logging system. --- # Simulation Examples ## Heating Example[​](#heating-example "Direct link to Heating Example") The heating example simulates a simple heating control system. The system consists of three services: a heater, a thermostat, and a temperature sensor. The heater can be turned on and off, and it influences the temperature in the room. The thermostat can set a target temperature and switch between auto and manual mode. The temperature sensor reads the current temperature and updates it based on the heater's influence. ### API Definition[​](#api-definition "Direct link to API Definition") ``` module heating interface Heater { isOn: bool power: int temperature: float maxTemp: float minTemp: float turnOn() turnOff() updateTemperature(deltaTime: int) } interface Thermostat { targetTemperature: float tolerance: float mode: string setTargetTemperature(temp: float) checkTemperature() setMode(newMode: string) } interface TempSensor { currentTemperature: float updateInterval: int lastUpdate: int update() } ``` ### Simulation Script[​](#simulation-script "Direct link to Simulation Script") ``` // Heater control system simulation const heater = $createService("heating.Heater", { isOn: false, power: 2000, // watts temperature: 20.0, // celsius maxTemp: 30.0, minTemp: 15.0 }); const thermostat = $createService("heating.Thermostat", { targetTemperature: 22.0, tolerance: 0.5, mode: 'auto' // 'auto' or 'manual' }); const tempSensor = $createService("heating.TempSensor", { currentTemperature: 20.0, updateInterval: 1000, // ms lastUpdate: Date.now() }); // Heater methods using natural API heater.turnOn = function () { if (!this.isOn) { this.isOn = true; console.log("Heater turned ON"); this.emit('stateChanged', true); } } heater.turnOff = function () { if (this.isOn) { this.isOn = false; console.log("Heater turned OFF"); this.emit('stateChanged', false); } } heater.updateTemperature = function (deltaTime) { if (this.isOn) { // Simple temperature increase model // Temperature rises faster when difference to max temp is larger const heatIncrease = (this.maxTemp - this.temperature) * 0.1; this.temperature += heatIncrease * (deltaTime / 1000); } else { // Natural cooling model // Temperature falls faster when difference to ambient temp is larger const cooling = (this.temperature - tempSensor.currentTemperature) * 0.05; this.temperature -= cooling * (deltaTime / 1000); } } // Thermostat methods using natural API thermostat.setTargetTemperature = function (temp) { if (temp >= heater.minTemp && temp <= heater.maxTemp) { this.targetTemperature = temp; console.log(`Target temperature set to ${temp}°C`); this.checkTemperature(); } else { console.log(`Temperature ${temp}°C is outside allowed range`); } } thermostat.checkTemperature = function () { const currentTemp = tempSensor.currentTemperature; const lowerBound = this.targetTemperature - this.tolerance; const upperBound = this.targetTemperature + this.tolerance; if (currentTemp < lowerBound) { heater.turnOn(); } else if (currentTemp > upperBound) { heater.turnOff(); } } thermostat.setMode = function (newMode) { if (newMode === 'auto' || newMode === 'manual') { this.mode = newMode; console.log(`Thermostat mode set to ${newMode}`); if (newMode === 'auto') { this.checkTemperature(); } } } // Temperature sensor methods using natural API tempSensor.update = function () { const now = Date.now(); const deltaTime = now - this.lastUpdate; this.lastUpdate = now; // Update current temperature based on heater's influence const heatTransfer = (heater.temperature - this.currentTemperature) * 0.1; this.currentTemperature += heatTransfer * (deltaTime / 1000); // Add some random fluctuation this.currentTemperature += (Math.random() - 0.5) * 0.1; console.log(`Current temperature: ${this.currentTemperature.toFixed(1)}°C`); if (thermostat.mode === 'auto') { thermostat.checkTemperature(); } } function main() { // Set up monitoring using natural API heater.on("isOn", function (isOn) { console.log(`Heater state changed to: ${isOn ? "ON" : "OFF"}`); }); tempSensor.on("currentTemperature", function (temp) { console.log(`Temperature sensor reading: ${temp.toFixed(1)}°C`); }); thermostat.on("targetTemperature", function (temp) { console.log(`Target temperature changed to: ${temp.toFixed(1)}°C`); }); // Listen to custom signal heater.on('stateChanged', function(state) { console.log(`Heater state signal: ${state ? "ON" : "OFF"}`); }); // Initial setup thermostat.setMode('auto'); thermostat.setTargetTemperature(23.0); // Want it a bit warmer // Simulate temperature changes over time const simulationSteps = 10; for (let i = 0; i < simulationSteps; i++) { tempSensor.update(); } return { finalTemperature: tempSensor.currentTemperature, heaterState: heater.isOn, targetTemperature: thermostat.targetTemperature }; } ``` ## Ball Animation Example[​](#ball-animation-example "Direct link to Ball Animation Example") The ball animation example simulates a simple ball physics system. The ball has position, velocity, and acceleration properties. The ball moves around based on its velocity and acceleration, demonstrating the natural API for property updates and signal emissions. ### API Definition[​](#api-definition-1 "Direct link to API Definition") ``` module game struct Vec2D { x: int y: int } interface Ball { pos: Vec2D vel: Vec2D acc: Vec2D move() reset() } ``` ### Simulation Script[​](#simulation-script-1 "Direct link to Simulation Script") ``` // Ball physics simulation using natural API const ball = $createService("game.Ball", { pos: { x: 0, y: 0 }, vel: { x: 1, y: 1 }, acc: { x: 1, y: 1 }, }); // Define move method using natural API with 'this' ball.move = function() { const acc = this.acc; const vel = this.vel; const pos = this.pos; // Calculate new position and velocity const newPos = { x: pos.x + vel.x, y: pos.y + vel.y }; const newVel = { x: vel.x + acc.x, y: vel.y + acc.y }; // Update properties using natural assignment this.pos = newPos; this.vel = newVel; // Emit movement signal this.emit('moved', newPos); }; // Reset method ball.reset = function() { this.pos = { x: 0, y: 0 }; this.vel = { x: 1, y: 1 }; this.emit('reset'); } // Monitor property changes using natural API ball.on("pos", function (value) { console.log("Position changed:", JSON.stringify(value)); }); ball.on("vel", function (value) { console.log("Velocity changed:", JSON.stringify(value)); }); ball.on("acc", function (value) { console.log("Acceleration changed:", JSON.stringify(value)); }); // Listen to custom signals ball.on('moved', function(newPos) { console.log(`Ball moved to: (${newPos.x}, ${newPos.y})`); }); ball.on('reset', function() { console.log('Ball was reset to initial position'); }); function main() { console.log("=== Ball Physics Simulation ==="); // Access raw properties when needed console.log("Initial state:", JSON.stringify(ball.$.getProperties())); // Run simulation for (let i = 0; i < 5; i++) { console.log(`\nStep ${i + 1}:`); ball.move(); } // Access final state through raw API console.log("\nFinal state:", JSON.stringify(ball.$.getProperties())); // Demonstrate reset console.log("\nResetting ball..."); ball.reset(); // Direct property access console.log("Position after reset:", JSON.stringify(ball.pos)); console.log("Velocity after reset:", JSON.stringify(ball.vel)); } ``` ## Vehicle Dashboard Example[​](#vehicle-dashboard-example "Direct link to Vehicle Dashboard Example") This example demonstrates a more complex simulation with multiple interconnected services representing a vehicle dashboard system. ### Simulation Script[​](#simulation-script-2 "Direct link to Simulation Script") ``` const state = $createService("vehicle.State", { location: { x: 0, y: 0 }, speed: 0, rpm: 0, fuelLevel: 50, fuelLevelWarning: false, temperature: 20, overheatWarning: false }); const indicators = $createService("vehicle.Indicators", { checkEngine: false, oilPressure: false, battery: false, airbag: false, brake: false, seatbelt: false, tractionControl: false, highBeam: false }); const commands = $createService("vehicle.Commands", {}); // Command methods using natural API commands.turnOn = function () { const order = ['checkEngine', 'oilPressure', 'battery', 'brake', 'seatbelt', 'tractionControl', 'highBeam']; let index = 0; const interval = setInterval(function() { if (index < order.length) { const indicator = order[index]; indicators[indicator] = true; console.log(`Turned on ${indicator}`); index++; } else { clearInterval(interval); commands.emit('allIndicatorsOn'); } }, 200); } commands.turnOff = function () { // Turn off all indicators using direct property access indicators.checkEngine = false; indicators.oilPressure = false; indicators.battery = false; indicators.airbag = false; indicators.brake = false; indicators.seatbelt = false; indicators.tractionControl = false; indicators.highBeam = false; this.emit('allIndicatorsOff'); } // Add method to state service for speed updates state.accelerate = function(amount = 10) { this.speed += amount; this.rpm = Math.min(8000, this.speed * 100); // Update fuel consumption this.fuelLevel = Math.max(0, this.fuelLevel - amount * 0.01); this.fuelLevelWarning = this.fuelLevel < 10; // Update temperature this.temperature = Math.min(120, this.temperature + amount * 0.1); this.overheatWarning = this.temperature > 100; } function main() { // Set up event monitoring with natural API state.on('speed', function(speed) { console.log(`Speed: ${speed} km/h`); }); state.on('fuelLevelWarning', function(warning) { if (warning) { console.log('⚠️ Low fuel warning!'); } }); state.on('overheatWarning', function(warning) { if (warning) { console.log('⚠️ Engine overheating!'); } }); commands.on('allIndicatorsOn', function() { console.log('All indicators checked'); }); // Monitor specific indicator indicators.on("checkEngine", function (value) { console.log("Check engine indicator:", value ? "ON" : "OFF"); }); // Run startup sequence commands.turnOn(); // Simulate driving let drivingInterval = setInterval(function() { state.accelerate(); if (state.speed >= 120 || state.fuelLevel <= 0) { clearInterval(drivingInterval); console.log('Stopping simulation'); commands.turnOff(); } }, 500); } ``` ## Key Features of the Natural API[​](#key-features-of-the-natural-api "Direct link to Key Features of the Natural API") The examples above demonstrate the key features of the new natural API: 1. **Direct Property Access**: Properties can be read and written directly using dot notation 2. **Automatic `this` Binding**: Methods automatically receive the service proxy as `this` 3. **Unified Event Handling**: The `on()` method handles both property changes and custom signals 4. **Signal Emission**: Use `this.emit()` to emit custom signals from within methods 5. **Raw API Access**: When needed, access the underlying service object via `service.$` This natural API provides a more intuitive JavaScript development experience while maintaining full compatibility with the ObjectLink protocol. --- # Service Simulation ## Why?[​](#why "Direct link to Why?") Simulation of services helps to predict the data and behavior of services through virtual objects. Simulation is used to diagnose problems and to test otherwise difficult to establish behavior. A typical use case is to enable designers to run a user interface without being depending or to wait for a service implementation. This gets even more important when the service runs on embedded or automotive systems where hardware access is often limited. An object is a virtual entity that can be created and destroyed and it has state and behavior as well notifies the world of changes. Objects are accessed through a protocol adapter that is implemented by the simulation server. The simulation shares the same API as a production service and can be used to validate the use of an API without having to implement the actual service. The simulation is interchangeable with a real service and can be used in the same way. ## Quick Start[​](#quick-start "Direct link to Quick Start") A simulation file is a JavaScript file which contains typically one simulation scenario. The file creates all API endpoints using an actor. The initial simulation can be auto generated by the template system and then extended by the developer or simply written by hand. To explain the concept we will use a simple example. ``` module demo interface Counter { count: int increment() decrement() } ``` The counter example shows a simple interface which can be used to increment or decrement a reactive counter. The simulation file will look like this: ``` // counter.js // create a service based on module and interface name and provide initial properties const counter = $createService("demo.Counter", { count: 0 }); // react to changes of the count property counter.on("count", function(value) { console.log("count changed to " + value); }); // provide an implementation for the increment operation counter.increment = function() { // modify the count property using 'this', this will trigger the count property change event this.count++; }; // provide an implementation for the decrement operation counter.decrement = function() { this.count--; }; // provide an autorun function which will be called when the simulation is started function main() { for (let i = 0; i < 10; i++) { counter.increment(); counter.decrement(); } } ``` To run the simulation we need to start the server and then run the simulation file. The server will then start the simulation and provide the simulation interface to the client. The simulation template will generate a simulation stub as also typescript type definitions to provide a better developer experience. ## Running the simulation[​](#running-the-simulation "Direct link to Running the simulation") To run the simulation file we now can use the following command: ``` apigear sim run counter.js ``` This contact the server and sends the simulation file to the simulation server and runs it. The simulation server will create actors and run the main function, if available. Now the simulation server waits for API calls from the client. Now you need a client to connect to the simulation server over ObjectLink and trigger the simulation. You can either: * Build a client using one of the ObjectLink client SDKs * Use the [stimulation tool](/docs/scripting/clients/intro.md) to create JavaScript-based test clients ## Simulation Protocol[​](#simulation-protocol "Direct link to Simulation Protocol") The underlying simulation protocol is the way to interact with a simulation server. It defines how to call operations, how to get the state of an interface and how to trigger sequences. The simulation server currently supports the ObjectLink protocol which is available in the [ObjectLink](/docs/protocols/objectlink/intro.md) section. --- # Simulation Protocol The simulation framework allows client to simulate interfaces using a simulation server. A client receives simulated responses or behavior based on a provided scenario document. The simulation server can be called using the ObjectLink protocol. ## Example[​](#example "Direct link to Example") In the following we will use always our counter example which looks like this: ``` module demo interface Counter { count: int increment(step: int) decrement(step: int) } interface Echo { say(message: string): string } ``` We can identify an operation using an URI like this `demo.Counter/increment` and a service like this `demo.Counter`. As a convention, calling the service should always give back the current state, which is the sum of properties. Calling an operation should always return a valid value defined by the `return` type. ## ObjectLink Protocol for Simulation[​](#objectlink-protocol-for-simulation "Direct link to ObjectLink Protocol for Simulation") The simulation server can be called using the ObjectLink protocol over WebSockets. The protocol is described in the [ObjectLink specification](/docs/protocols/objectlink/intro.md). An incoming object link message is wired to a scenario document. Each scenario document can contain multiple interfaces. The interface name is used to identify the interface to be simulated. The operation name is used to identify the operation to be simulated. In case no scenario document is provided, the simulation server will use a default scenario document. ## Feeding the Simulation Server[​](#feeding-the-simulation-server "Direct link to Feeding the Simulation Server") The simulation server can be fed with a a set of JSON messages arranged in a new-line separated JSON file. The file can be provided using the `--feed` command line option of the CLI. The file can be used to feed the simulation server with a set of messages. The messages are sent to the simulation server in the order they are provided in the file. The file can be used to feed the simulation server with a set of messages. The messages are sent to the simulation server in the order they are provided in the file. Feeding a simulation server is a great way to validate your simulation scenario or even your object-link service. --- # Simulation Scenarios A scenario captures the data and behavior of interfaces (aka services). It is used to simulate the behavior of a service. A scenario is a JavaScript file which contains a simulation scenario. The scenario creates all API endpoints using an actor. The initial simulation can be auto generated by the template system and then extended by the developer or simply written by hand. A scenario is loaded and executed into the simulation server. For this the simulation server has to be started before using (`apigear serve`) or run with the simulation file (`apigear sim run --serve`). In case the simulation server is already running the simulation file can be loaded using the `apigear sim run ` command. ## API Definition[​](#api-definition "Direct link to API Definition") Here is a simple API definition and the corresponding simulation scenario: ``` module demo interface Counter { count: int increment() decrement() } ``` The counter example shows a simple interface which can be used to increment or decrement a reactive counter. ## Simulation file[​](#simulation-file "Direct link to Simulation file") The simulation file will look like this: ``` // counter.sim.js // create a service based on module and interface name and provide initial properties const counter = $createService("demo.Counter", { count: 0 }); // react to changes of the count property counter.on("count", function(value) { console.log("count changed to " + value); }); counter.increment = function() { this.count++; }; counter.decrement = function() { this.count--; }; // a main function which will be called when the simulation is started function main() { for (let i = 0; i < 10; i++) { counter.increment(); counter.decrement(); } } ``` You run the simulation by starting the server and then run the simulation file. The server will then start the simulation and provide the simulation interface to the client. ``` # run a simulation and start the server $ apigear sim run counter.sim.js ``` --- # Generator Based Simulation Besides writing simulation scenarios by hand, you can also use the generator to create simulation files based on your API definitions. This is particularly useful for larger APIs or when you want to quickly set up a simulation environment. ## How It Works[​](#how-it-works "Direct link to How It Works") The generator analyzes your API definitions and automatically creates stubbed simulation scenarios. These scenarios include basic implementations of the interfaces defined in your API, allowing you to quickly test and simulate the behavior of your services without having to write all the code manually. ## Benefits[​](#benefits "Direct link to Benefits") * **Speed**: Quickly generate a comprehensive set of simulation scenarios without having to write them manually. * **Coverage**: Ensure that all aspects of your API are covered by the generated simulations. * **Consistency**: Maintain a consistent approach to simulation across your API definitions. ## Getting Started[​](#getting-started "Direct link to Getting Started") To use the generator, you need to add a new target to your solution file. This target will specify the API definitions you want to generate simulations for. Here is an example of how to add a target for simulation generation: ```` # my.solution.yaml schema: apigear.solution/1.0 targets: - name: My Simulation output: sim inputs: - counter.yaml # or counter.idl template: apigear-io/template-simulation/ ```bash apigear generate solution my.solution.yaml ```` This will create a new directory (`sim`) containing all the generated simulation files. You can then customize these files as needed to fit your specific requirements. To run the generated simulations, you can use the same commands as for manually written scenarios: ``` apigear simulation run sim/counter.sim.js ``` This will start the simulation server and load the generated simulation scenario and run the main function defined in the simulation file. ## Visual Code Editor Support[​](#visual-code-editor-support "Direct link to Visual Code Editor Support") With the simulation files we also generate js types for the interfaces, structs and enums. This allows you to use the Visual Code Editor to edit the simulation files with type checking and autocompletion support. This should help you to write and maintain your simulation scenarios more easily. If you are interested please check out the generated `api` directory in the simulation output. It contains the type definitions for your API. --- # Examples This section contains examples for scripted clients that connect to remote services. ## Counter Client[​](#counter-client "Direct link to Counter Client") A simple client that connects to a remote counter service and calls the increment method: ``` // counter_client.js const channel = $createChannel(); const client = channel.createClient("counter"); // Monitor property changes from the remote service client.onProperty("count", function (value) { console.log("client: count changed to", value); }); function main() { console.log("Counter client started"); // Call the remote increment method multiple times for (let i = 0; i < 5; i++) { console.log(`Calling increment (${i + 1}/5)`); client.callMethod("increment"); } console.log("All increment calls sent"); } ``` Run the client (requires a running counter service): ``` apigear sim run counter_client.js --no-serve ``` ## Vehicle Client[​](#vehicle-client "Direct link to Vehicle Client") A more complex example connecting to multiple remote services: ``` // vehicle_client.js const channel = $createChannel(); const commands = channel.createClient("vehicle.Commands"); const state = channel.createClient("vehicle.State"); const indicators = channel.createClient("vehicle.Indicators"); // Monitor state changes state.onProperty("speed", function(speed) { console.log(`Client - Speed: ${speed} km/h`); }); state.onProperty("fuelLevelWarning", function(warning) { if (warning) { console.log("Client - Low fuel warning!"); } }); indicators.onProperty("checkEngine", function(value) { console.log(`Client - Check engine: ${value}`); }); function main() { console.log("Vehicle client starting..."); // Turn on vehicle systems commands.callMethod("turnOn"); // Wait a bit then turn off setTimeout(function() { console.log("Turning off vehicle systems..."); commands.callMethod("turnOff"); }, 3000); } ``` ## Client API Reference[​](#client-api-reference "Direct link to Client API Reference") ### Creating a Channel[​](#creating-a-channel "Direct link to Creating a Channel") ``` const channel = $createChannel(); // Uses default ws://localhost:4333/ws const channel = $createChannel("ws://custom-host:5555/ws"); ``` ### Creating a Client[​](#creating-a-client "Direct link to Creating a Client") ``` const client = channel.createClient("service.name"); ``` ### Calling Methods[​](#calling-methods "Direct link to Calling Methods") ``` client.callMethod("methodName"); client.callMethod("methodName", arg1, arg2); ``` ### Monitoring Properties[​](#monitoring-properties "Direct link to Monitoring Properties") ``` client.onProperty("propertyName", function(value) { console.log("Property changed:", value); }); ``` ### Listening for Signals[​](#listening-for-signals "Direct link to Listening for Signals") ``` client.onSignal("signalName", function(args) { console.log("Signal received:", args); }); ``` --- # Scripted Clients ## Why?[​](#why "Direct link to Why?") Stimulation allows to trigger sequences of operations or data changes on a service. This is useful to test the behavior of a service or to simulate a user interaction. ## Stimulation file?[​](#stimulation-file "Direct link to Stimulation file?") A stimulation file is a JavaScript file which contains a stimulation scenario. The stimulation comes in the form of a client which can be used to trigger sequences of operations or data changes on a service. Base on this API example we can create a client which can be used to trigger sequences of operations or data changes on a service. ``` module demo interface Counter { count: int increment() decrement() } ``` The counter example shows a simple interface which can be used to increment or decrement a reactive counter. The stimulation file will look like this: ``` // counter_client.js const url = "ws://localhost:5555/ws" const channel = $createChannel(url) // create a client based on module and interface name const counter = channel.createClient("demo.Counter"); // Monitor property changes counter.onProperty("count", function(value) { console.log("Count changed to:", value); }); // Listen for signals if any counter.onSignal("countReached", function(target) { console.log("Count reached:", target); }); function main() { // main is auto run on script execution // Connect to the remote service channel.connect(); // Trigger operations for (let i = 0; i < 10; i++) { counter.callMethod("increment"); counter.callMethod("decrement"); } // Get current property value const currentCount = counter.getProperty("count"); console.log("Final count:", currentCount); // Optionally disconnect when done setTimeout(function() { channel.disconnect(); $quit(); }, 1000); } ``` To run the stimulation file we need to start a remote service ## Running the stimulation[​](#running-the-stimulation "Direct link to Running the stimulation") We will use a simulation file to simulate a service. ``` apigear sim run counter_service.js ``` This will run the simulation and provide the simulation interface to the client. Now we can run the stimulation file: ``` // run simulation client but don't create a server apigear sim run counter_client.js --no-serve ``` This will run the stimulation file and trigger the operations on the simulation interface. --- # Scripting ApiGear provides a powerful JavaScript-based scripting environment that lets you create dynamic behaviors for your APIs without writing compiled code. This is invaluable during development, testing, and demonstration phases. ## Why Scripting?[​](#why-scripting "Direct link to Why Scripting?") When developing systems with defined APIs, you often need to: * **Test client code** before the backend is ready * **Simulate complex behaviors** that are hard to reproduce manually * **Demo features** without deploying full infrastructure * **Prototype interactions** between services quickly ApiGear's scripting capabilities address these needs with two complementary approaches. ## Two Sides of Scripting[​](#two-sides-of-scripting "Direct link to Two Sides of Scripting") ``` ┌─────────────────┐ ┌─────────────────┐ │ │ │ │ │ Scripted │◄───── API ────────►│ Scripted │ │ Clients │ Calls │ Backends │ │ │ │ │ │ (Stimulation) │ │ (Simulation) │ │ │ │ │ └─────────────────┘ └─────────────────┘ Calls TO Responds TO your service your client ``` ### Scripted Backends[​](#scripted-backends "Direct link to Scripted Backends") **Simulate your service** — Create mock implementations that respond to API calls from your real client code. Use scripted backends when you want to: * Develop client applications before the backend exists * Test edge cases and error conditions * Run demos without production dependencies * Create reproducible test scenarios The scripted backend acts as a stand-in for your real service, responding to method calls, maintaining property state, and emitting signals. [Learn more about Scripted Backends →](/docs/scripting/backends/intro.md) ### Scripted Clients[​](#scripted-clients "Direct link to Scripted Clients") **Stimulate your service** — Create automated clients that call your real backend to test its behavior under various conditions. Use scripted clients when you want to: * Load test your service with realistic traffic patterns * Automate integration testing * Verify service behavior with scripted sequences * Monitor how your service responds to specific inputs The scripted client connects to your running service and executes predefined sequences of API calls. [Learn more about Scripted Clients →](/docs/scripting/clients/intro.md) ## Scripting Environment[​](#scripting-environment "Direct link to Scripting Environment") Both scripted backends and clients use the same JavaScript runtime with access to: * **Full API definitions** — Properties, methods, and signals from your ObjectAPI modules * **Timing controls** — Delays, intervals, and scheduled actions * **State management** — Read and modify property values * **Event handling** — React to property changes and signals * **Console output** — Log messages for debugging Scripts are executed by the ApiGear CLI or Studio, connecting via WebSocket to interact with the system. ## Getting Started[​](#getting-started "Direct link to Getting Started") 1. **Define your API** using ObjectAPI 2. **Generate simulation files** (optional) or write scripts manually 3. **Run the script** with the CLI: `apigear sim run script.js` Choose the approach that fits your development phase: * Building a client? Start with [Scripted Backends](/docs/scripting/backends/intro.md) to mock your service * Building a service? Start with [Scripted Clients](/docs/scripting/clients/intro.md) to test your implementation --- # SDK Templates ApiGear provides a collection of SDK templates that transform your API definitions into production-ready code for various programming languages and platforms. Each template generates clean interfaces, data structures, and build system support based on your API modules. Templates can be configured with different features and scale with your project as you add more API modules. ## Available Templates[​](#available-templates "Direct link to Available Templates") ### C++17[​](#c17 "Direct link to C++17") Modern C++17 SDK with CMake and Conan support. Generates abstract interfaces, data structures, and full build infrastructure. **Features:** API interfaces, scaffolding, OLink protocol, MQTT support, monitoring [View C++17 Documentation →](/template-cpp17/docs/intro.md) *** ### C++14[​](#c14 "Direct link to C++14") C++14 compliant SDK for projects requiring older compiler support. Currently in maintenance mode. **Features:** API interfaces, scaffolding, CMake, Conan, test support [View C++14 Documentation →](/template-cpp14/docs/intro.md) *** ### Qt6[​](#qt6 "Direct link to Qt6") Qt6/QML SDK generating CMake-based libraries and QtQuick plugins. Ideal for Qt-based desktop and embedded applications. **Features:** API interfaces, scaffolding, QML integration, OLink protocol, monitoring [View Qt6 Documentation →](/template-qt6/docs/intro.md) *** ### Unreal Engine[​](#unreal-engine "Direct link to Unreal Engine") Unreal Engine 5 SDK generating Blueprints and C++ classes. Perfect for game development and interactive applications. **Features:** API interfaces, scaffolding, Blueprint support, OLink protocol, monitoring [View Unreal Documentation →](/template-unreal/docs/intro.md) *** ### Python[​](#python "Direct link to Python") Python SDK generating modules with clean interfaces and type hints. Suitable for backend services and scripting. **Features:** API interfaces, scaffolding, test support, OLink protocol [View Python Documentation →](/template-python/docs/intro.md) *** ## Template Features[​](#template-features "Direct link to Template Features") All templates support common features that can be enabled or disabled: | Feature | Description | | ------------ | ------------------------------------------------------ | | **API** | Generates interface definitions and data structures | | **Scaffold** | Creates a complete project with build system and tests | | **OLink** | Enables remote object communication protocol | | **MQTT** | Adds MQTT messaging support (select templates) | | **Monitor** | Integrates with ApiGear monitoring tools | ## Getting Started[​](#getting-started "Direct link to Getting Started") 1. Define your API using the [ObjectAPI format](/docs/objectapi/intro.md) 2. Create a [solution file](/docs/guide/quick-start.md#solution-file) specifying your template 3. Run the code generator using [CLI](/docs/cli/intro.md) or [Studio](/docs/studio/intro.md) 4. Follow the generated README for build and integration instructions --- # Filter Reference ## Template Filters[​](#template-filters "Direct link to Template Filters") String filters are used inside templates to transform text in different formats. A typical usage would use the filter name followed by a string reference to the value to be transformed. For example: ``` {{ snake .Module.Name }} ``` Where `.` is the current context and `.Module` is the module object from the current context and `.Name` is the name property of the module object. The context can change for example inside a range loop. ``` {{ range .Module.Interfaces }} {{ snake .Name }} {{ end }} ``` Where the current context if the interface object inside the range loop. ## String Filter[​](#string-filter "Direct link to String Filter") The list of the common string filters are listed here ### **snake**, **Snake**, **SNAKE**[​](#snake-snake-snake "Direct link to snake-snake-snake") Converts a string to snake case - (lower, title, upper) case with underscores ``` {{snake "org.demo"}} => org_demo {{Snake "org.demo"}} => Org_Demo {{SNAKE "org.demo"}} => ORG_DEMO ``` ### **camel**, **Camel**, **CAMEL**[​](#camel-camel-camel "Direct link to camel-camel-camel") Converts a string to camel case - (lower, title, upper) case with first letter lower case ``` {{camel "org.demo"}} => orgDemo {{Camel "org.demo"}} => OrgDemo {{CAMEL "org.demo"}} => ORGDEMO ``` ### **dot**, **Dot**, **DOT**[​](#dot-dot-dot "Direct link to dot-dot-dot") Converts a string to dot case - (lower, title, upper) case with dots ``` {{dot "org.demo"}} => org.demo {{Dot "org.demo"}} => Org.Demo {{DOT "org.demo"}} => ORG.DEMO ``` ### **kebap**, **Kebap**, **KEBAP**[​](#kebap-kebap-kebap "Direct link to kebap-kebap-kebap") Converts a string to kebap case - (lower, title, upper) case with dashes ``` {{kebap "org.demo"}} => org-demo {{Kebap "org.demo"}} => Org-Demo {{KEBAP "org.demo"}} => ORG-DEMO ``` ### **path**, **Path**, **PATH**[​](#path-path-path "Direct link to path-path-path") Converts a string to path case - (lower, title, upper) case with slashes ``` {{path "org.demo"}} => org/demo {{Path "org.demo"}} => Org/Demo {{PATH "org.demo"}} => ORG/DEMO ``` ### **lower**[​](#lower "Direct link to lower") Converts a string to lower case ``` {{lower "org.demo"}} => org.demo ``` ### *upper*\*[​](#upper "Direct link to upper") Converts a string to upper case ``` {{upper "org.demo"}} => ORG.DEMO ``` ### **upperFirst**[​](#upperfirst "Direct link to upperfirst") Converts the first letter of a string to upper case ``` {{upper1 "org.demo"}} => Org.demo ``` ### **lowerFirst**[​](#lowerfirst "Direct link to lowerfirst") Converts the first letter of a string to lower case ``` {{lower1 "org.demo"}} => org.demo ``` ### **first**, **First**, **FIRST**[​](#first-first-first "Direct link to first-first-first") Returns the first character of a string as lower, unchanged, upper case ``` {{first "org.demo"}} => o {{First "org.demo"}} => o {{FIRST "org.demo"}} => O ``` ### **join**[​](#join "Direct link to join") joins a list of strings with a separator ``` {{join .Module.Interfaces ", "}} => org.demo.Interface1, org.demo.Interface2 ``` ### **trimPrefix**[​](#trimprefix "Direct link to trimprefix") Trims a prefix from a string ``` {{trimPrefix "org.demo" "org."}} => demo ``` ### **trimSuffix**[​](#trimsuffix "Direct link to trimsuffix") Trims a suffix from a string ``` {{trimSuffix "org.demo" ".demo"}} => org ``` ### **replace**[​](#replace "Direct link to replace") Replaces a string with another string ``` {{replace "org.demo" "org" "com"}} => com.demo ``` ### **int2word**, **Int2Word**, **INT2WORD**[​](#int2word-int2word-int2word "Direct link to int2word-int2word-int2word") Converts an integer to words (lower, title and upper case) ``` {{int2word 1}} => one {{Int2Word 1}} => One {{INT2WORD 1}} => ONE ``` ### **plural**[​](#plural "Direct link to plural") pluralizes a string ``` {{plural "org.demo"}} => org.demos ``` ### **nl**[​](#nl "Direct link to nl") prints a new line ``` {{nl}} ``` ### **version**[​](#version "Direct link to version") extracts major, minor, build version from a version string ``` {{$v := version "1.2.3"}} {{$v}} => 1.2.3 {{$v.Major}} => 1 {{$v.Minor}} => 2 {{$v.Build}} => 3 ``` ## Language Filters[​](#language-filters "Direct link to Language Filters") All programming languages share a common set of filters which then are adapted to the specifics of the language. These are `return`, `param`, `params`, `vars`, `names` and the `default` filter. For example for the C++ programming language you would use the `return` and `params` filter named `cpp14Return` and `cpp14Params` during operation declaration. A typical usage could look like this: ``` {{ range .Operations }} {{ cppReturn "" .Return }} {{ camel .Name }}({{ cppParams "" .Params }}); {{ end }} ``` Here the `cppReturn` and `cppParams` are the language specific filters for the C++ programming language. The `""` is the a prefix applied to the return type and the parameters. This is used to add a namespace to the return type and the parameters. All language specific filters have the same signature. tip We might offer a "2" version of a language filter in the future (e.g. `cppReturn` and `cppReturn2`) where the second version will support the prefix syntax. These are the common filters for all languages ### **{lang}Return**[​](#langreturn "Direct link to langreturn") Takes and typed element and returns the type declaration of the type ``` {{ range .Module.Interfaces }} {{ range .Operations }} {{ cppReturn "" .Return }} {{ camel .Name }}({{ cppParams "" .Params }}); {{ end }} {{ end }} ``` ### **{lang}Param**[​](#langparam "Direct link to langparam") Takes a typed element and returns the function parameter declaration ``` {{ range .Module.Interfaces }} {{ range .Operations }} {{ cppReturn "" .Return }} {{ camel .Name }}( {{ range $i, $p := .Params }} {{ if $i }}, {{ end }} {{ cppParam "" $p }} {{ end }}); {{ end }} {{ end }} ``` ### **{lang}Params**[​](#langparams "Direct link to langparams") Takes an operation and return the lists of function parameters ``` {{ range .Module.Interfaces }} {{ range .Operations }} {{ cppReturn "" .Return }} {{ camel .Name }}({{ cppParams "" .Params }}); {{ end }} {{ end }} ``` ### **{lang}Default**[​](#langdefault "Direct link to langdefault") Takes a typed element and returns default value ``` {{ range .Module.Interfaces }} {{ $class := .Name }} {{ range .Operations }} {{ cppReturn "" .Return }} {{$class}}::{{ camel .Name }}({{ cppParams "" .Params }}) { return {{ cppDefault "" .Return }}; } {{ end }} {{ end }} ``` ### **{lang}Vars**[​](#langvars "Direct link to langvars") Takes a list of types and creates variable names for them ``` {{ cppVars "" .Properties }} ``` ### **{lang}Var**[​](#langvar "Direct link to langvar") Takes a typed element and creates a variable name for it ``` {{ cppVar "" .Property }} ``` ### **{lang}Type**[​](#langtype "Direct link to langtype") Takes a typed element and returns the type declaration of the type ``` {{ cppType "" .Property }} ``` ## C++14 Filters[​](#c14-filters "Direct link to C++14 Filters") * **cppReturn**: takes a typed element and returns the type declaration of the type * **cppParam**: takes a typed element and returns the function parameter declaration * **cppParams**: takes an operation and return the lists of function parameters * **cppDefault**: takes a typed element and returns default value * **cppVars**: takes a list of types and creates variable names for them * **cppVar**: takes a typed element and creates a variable name for it * **cppType**: takes a typed element and returns the type declaration of the type * **cppConstType**: takes a typed element and returns the type declaration of the type with const qualifier * **cppNs**: takes a symbol and returns the namespace declaration * **cppNsOpen**: takes a symbol and returns the namespace opening * **cppNsClose**: takes a symbol and returns the namespace closing * **cppGpl**: takes a symbol and returns the GPL license header ## Go Filters[​](#go-filters "Direct link to Go Filters") * **goReturn**: takes a typed element and returns the type declaration of the type * **goParam**: takes a typed element and returns the function parameter declaration * **goParams**: takes an operation and return the lists of function parameters * **goDefault**: takes a typed element and returns default value * **goVars**: takes a list of types and creates variable names for them * **goVar**: takes a typed element and creates a variable name for it * **goType**: takes a typed element and returns the type declaration of the type ## TypeScript Filters[​](#typescript-filters "Direct link to TypeScript Filters") * **tsReturn**: takes a typed element and returns the type declaration of the type * **tsParam**: takes a typed element and returns the function parameter declaration * **tsParams**: takes an operation and return the lists of function parameters * **tsDefault**: takes a typed element and returns default value * **tsVars**: takes a list of types and creates variable names for them * **tsVar**: takes a typed element and creates a variable name for it * **tsType**: takes a typed element and returns the type declaration of the type ## QtC++ Filters[​](#qtc-filters "Direct link to QtC++ Filters") * **qtReturn**: takes a typed element and returns the type declaration of the type * **qtParam**: takes a typed element and returns the function parameter declaration * **qtParams**: takes an operation and return the lists of function parameters * **qtDefault**: takes a typed element and returns default value * **qtVars**: takes a list of types and creates variable names for them * **qtVar**: takes a typed element and creates a variable name for it * **qtType**: takes a typed element and returns the type declaration of the type ## Python Filters[​](#python-filters "Direct link to Python Filters") * **pyReturn**: takes a typed element and returns the type declaration of the type * **pyParam**: takes a typed element and returns the function parameter declaration * **pyParams**: takes an operation and return the lists of function parameters * **pyDefault**: takes a typed element and returns default value * **pyVars**: takes a list of types and creates variable names for them * **pyVar**: takes a typed element and creates a variable name for it * **pyType**: takes a typed element and returns the type declaration of the type ## Unreal Engine Filters[​](#unreal-engine-filters "Direct link to Unreal Engine Filters") * **ueReturn**: takes a typed element and returns the type declaration of the type * **ueParam**: takes a typed element and returns the function parameter declaration * **ueParams**: takes an operation and return the lists of function parameters * **ueDefault**: takes a typed element and returns default value * **ueVars**: takes a list of types and creates variable names for them * **ueVar**: takes a typed element and creates a variable name for it * **ueType**: takes a typed element and returns the type declaration of the type * **ueConstType**: takes a typed element and returns the type declaration of the type with const qualifier --- # Introduction ApiGear offers a large set of pre-made technology templates covering the major programming languages and technologies. But often a project requires some adaptations to the templates or require support for a different technology which is not yet covered. ApiGear was designed to allow customers easily to adapt the existing templates for the specifics of each customer project. Customers can provide their own technology templates, either as open source or proprietary templates. The process to adapt an existing template or to create a fresh new template is not really difficult and will be covered here. ## Maker[​](#maker "Direct link to Maker") A user which creates templates is called a maker. Normally is is possible to contribute to existing templates to enhance in an open discussion the quality for everyone. But often there is also a desire to solve a new problem or to create a project specific adaptation. In these cases it makes sense to fork a template or to create a new template. templates allows you to solve all kind of API structured problems in a very easy way. ## Creating a template[​](#creating-a-template "Direct link to Creating a template") A template is a folder with a common structure consisting of a rules document, which controls the code generation process and the templates folder. The templates will be listed inside a rules document. Later a solution document will link the template with API modules for code generation. Below is roughly the process to create and develop a template * Create a template folder with rules document and templates folder * Create an ApiGear Studio API project with a demo API * Add a solution to the API project to link API modules with the template * Edit the template and re-generate the output The templates are written using the [go-template](https://pkg.go.dev/text/template) template language and each template should end with `.tpl`. ApiGear extends the template language by custom programming language specific filters to ease many complex tasks. The Go-Template is used in several projects and is well documented. The ApiGear extensions are documented in the [Filters](/docs/sdk/maker/filters.md) section. ## Your first template[​](#your-first-template "Direct link to Your first template") A typical template folder structure looks like this ``` first/ rules.yaml templates/welcome.txt.tpl ``` The rules document is a YAML document and lists all template file and how they shall be written inside the output folder. A simple rules document could look like this: ``` features: - name: default scopes: - match: system documents: - source: welcome.txt.tpl target: welcome.txt ``` The first level (here `default`) defines a feature. This can be enabled or disabled using the feature settings for the code generator. The next level (here `system`) defines to which symbol is shall be applied. A symbol is a defined location inside the API description. Inside an API there exists the following symbols: * `system`: will be applied once for the root level system * `module`: will be applied for each module in the system * `interface`: will be is applied for each interface inside the module * `struct`: will be is applied for each struct inside the module * `enum`: will be is applied for each enum inside the module For each of these symbols inside the API description the target documents are written based on the source template. You could think of the underlying logic of a rules document like this (using a python pseudo-code): ``` system = ... write_system_documents() for module in system.modules: write_module_documents() for interface in module.interfaces: write_interface_documents() for struct in module.structs: write_struct_documents() for enum in module.enums: write_enum_documents() ``` Here is a more elaborate example of a rules document: ``` # rules.yaml features: - name: default scopes: - match: system documents: - source: system.md.tpl target: system.md - match: module documents: - source: module.md.tpl target: {{.Module.Name}}.md - match: interface: documents: - source: interface.md.tpl target: {{.Interface.Name}}.md - match: struct documents: - source: struct.md.tpl target: {{.Struct.Name}}.md - match: enum documents: - source: enum.md.tpl target: {{.Enum.Name}}.md - name: summary scopes: - match: system documents: - source: summary.md.tpl target: summary.md ``` The `{{ }}` are a template expression. For example `{{.Module.Name}}` will be replaced with the content of module name. This allows you to write exactly these document names you require. Additional to the template tags, the template engine also support filters. A filter is a function which takes a object and return a string. For example `{{ lower .Module.Name }}` or `{{ .Module.Name | lower }}` will lower case the module name. There are more filters in the in our filters documentation. --- # Rules Document The rules document defines the context for each document to be transformed. ## Features[​](#features "Direct link to Features") Features are an isolated part of the code generator. When running the code generator features can be enabled and disable. A feature is declared by name inside the features root tag. ``` features: - name: feature1 - name: feature2 ``` A feature can contain a prefix target path as also a scope for documents. ``` features: - name: feature1 prefix: {{module.name}}/ ``` Typical features are `api`, `scaffold` but also protocols like `http`, `wamp`. A feature can have these properties: * `needs`: defines feature flag which other features need to be enabled * `prefix`: defines a prefix output path * `scope`: defines a scope for documents with a match expression (e.g. `system`, `module`, `interface`, `struct`, `enum`) ### Feature dependencies[​](#feature-dependencies "Direct link to Feature dependencies") Needs allows to define a dependency between features. For example the `scaffold` feature needs the `api` feature to be enabled. ``` features: - name: api - name: scaffold needs: - api ``` ## Scopes[​](#scopes "Direct link to Scopes") A scope defined the context for the template for language for the defined documents. The context defines the available objects available inside the template language. For example a module scope will always have defined `features`, `system` and `module` in the template document. ``` features: - name: feature1 scopes: - match: system documents: - { source: system.go, target: system.go } - match: module documents: - { source: module.go, target: module.go } - match: interface documents: - { source: interface.go, target: interface.go } - match: struct documents: - { source: struct.go, target: struct.go } - match: enum documents: - { source: enum.go, target: enum.go } ``` A scope with the match `interface` iterates over all interfaces in all modules and has defined a context with `features`, `system` and `module` and the current `interface` in it. ### Match logic[​](#match-logic "Direct link to Match logic") * match: `system` * called once for the system * context => `{ features, system }` * match: `module` * called for each module * context => `{ features, system, module }` * match: `interface` * called once per interface in all modules * context => `{ features, system, module, interface }` * match: `struct` * called once per struct in all modules * context => `{ features, system, module, struct }` * match: `enum` * called once per enum in all modules * context => `{ features, system, module, enum }` ## Documents[​](#documents "Direct link to Documents") A document defines the source template and the target where to write the document. The document define the source, target and some additional flags for writing. * `source`: source path inside the templates directory. * `target`: target template string inside the output directory. * `force`: if true, the document will be forced to be overwritten when re-generated. * `raw`: if true, document will be just copied and not treated as template. Raw if off by default. --- # Symbol Reference When developing technology templates it is vital to understand what each symbol traversing has to offer on properties. ObjectAPI is structured based on the idea of modules, interfaces, structures and enumerations. And an interface contains properties, operations and signals and structures contain fields and enumerations contain members. ``` type System struct { Name string Modules []Module } type Module struct { Name string Interfaces []Interface Structures []Structure Enumerations []Enumeration } type Interface struct { Name string Properties []TypedElement Operations []Operation Signals []Signal } type TypedElement struct { Name string Type Type } type Operation struct { Name string Params []TypedElement Return Type } type Signal struct { Name string Params []TypedElement } type Structure struct { Name string Fields []TypedElement } type Enumeration struct { Name string Members []ValueElement } type ValueElement struct { Name string Value string } ``` Each symbol you can iterate over is either element or an element with additional type information. ## Named Element[​](#named-element "Direct link to Named Element") Named element is the base element for all symbols. It contains the following properties: * **Name**: string * name of the element * **Description**: string * test to describe the element * **Meta**: object * free form data, evaluated by the technology template * **Kind**: string * one of \[module, interface, struct, enum, property, operation, signal] ## Typed Element[​](#typed-element "Direct link to Typed Element") Typed element is a named element with additional properties for typing * **Type** type information * one of \[bool, int, float, string] or reference to interface, struct, enum * **Array**: true if the type is an array * **IsPrimitive**: primitive type * type is one of \[bool, int, float, string] * **IsSymbol**: is reference to type * type is one of struct, enum, interface * **IsPrimitiveArray**: array with items of primitive type * **IsSymbolArray**: array with item of reference to type * **IsArray**: true if the type is an array ## Module[​](#module "Direct link to Module") The module is an element with additional version and an information object. Additional it contains a list of interfaces, structures and enumerations All information from the element plus * **Version**: string * **Info**: information object * **Interfaces**: array of interface elements * **Structs**: array of struct elements * **Enums**: array of enum elements ### Information[​](#information "Direct link to Information") The information object allows customers to provide additional information about the API. * **Title**: string * **Description**: string * **TermsOfService**: string * **Contact**: is an object consisting of **name**, **url** and **email** * **License**: is an object containing of **name** and **url** ## Interface[​](#interface "Direct link to Interface") The interface is an container element for properties, operations and signals. All information from the element plus * **Properties**: array of property elements * **Operations**: array of operation elements * **Signals**: array of signal elements ### Property[​](#property "Direct link to Property") Property is a typed element. All information from the typed element. ### Operation[​](#operation "Direct link to Operation") Operation is a named element with an additional list of typed parameters. The return element defines the return type. All information from the typed element plus * **Params**: array of typed elements * **Return**: type information ### Signal[​](#signal "Direct link to Signal") Signal is a typed element and has the same signature as operation, besides the return type information is ignored. All information from the typed element plus * **Params**: array of typed elements ## Structure[​](#structure "Direct link to Structure") Structure is an element and act as a container for fields. All information from the element, plus * **Fields**: array of typed elements ### Field[​](#field "Direct link to Field") Field is a typed element. All information from the typed element ## Enumeration[​](#enumeration "Direct link to Enumeration") Enumeration is an element and act as a container for members. All information from the element, plus * **Members**: array of member ### Member[​](#member "Direct link to Member") Member is a element with an additional value. All information from the named element, plus * **Value**: the value of the member --- # Template Primer The template language is based on Go Text Templates. The template language is described in the [Go Text Template documentation](https://golang.org/pkg/text/template/). To use a template you need to define a template file with the extension `.tpl`. Inside the template file you can use the template language to generate text, which is then written to the target file. ## Basic Syntax[​](#basic-syntax "Direct link to Basic Syntax") The template syntax writes the text as is to the target file. Actions inside the text allow to control the output. A action is a text inside `{{` and `}}`. The action can be a variable, a function or a control structure. To access variables you can use the current scope `.`. All variables are title case. For example to access the name of the current module you can use the following template: ``` {{ .Module.Name }} ``` ## Loops[​](#loops "Direct link to Loops") To iterate over a list you can use the `range` keyword. For example to iterate over all interfaces in the current module: ``` {{ range .Module.Interfaces }} {{ .Name }} {{ end }} ``` See how the current context changes inside the range loop. A loop can also be empty and the `else` keyword can be used to define the empty case. For example to iterate over all interfaces in the current module: ``` {{ range .Module.Interfaces }} {{ .Name }} {{ else }} No interfaces found {{ end }} ``` To use the index and current element you can ``` {{ range $index, $element := .Module.Interfaces }} {{ $index }}: {{ $element.Name }} {{ end }} ``` Index is a zero-based iterator and element is the current element in the loop. tip To join lists with commas you can use the loop index. ``` {{ range $i, $e := .Module.Interfaces }} {{ if $i}},{{ end }}{{ .Name }} {{ end }} ``` A comma will be printed when `$i` is not zero. ## Assignments[​](#assignments "Direct link to Assignments") To define new variable you can assign a value to a variable. For example to assign the name of the current module to a variable: ``` {{ $name := .Module.Name }} ``` tip This allows you to define a set of variables at the beginning of the document and used them throughout. For example a class name for the current interface: ``` {{ $class := Camel .Name }} ``` ## Conditionals[​](#conditionals "Direct link to Conditionals") You can use an if-end or if-else-end construct to control the output. For example to only output the name of the current interface if it is not empty: ``` {{ if .Name }} {{ .Name }} {{ end }} ``` or to output a default value if the name is empty: ``` {{ if .Name }} {{ .Name }} {{ else }} {{ .Module.Name }} {{ end }} ``` ## White Space[​](#white-space "Direct link to White Space") The template language allows to control the white space. For example to remove the white space before the output: ``` {{- if .Name }} {{ .Name }} {{- end }} ``` or to remove the white space after the output: ``` {{ if .Name -}} {{ .Name }} {{ end }} ``` ## String Formatting[​](#string-formatting "Direct link to String Formatting") You can use the `printf` function to format strings. For example to prefix an interface name with `I`: ``` {{ printf "I%s" .Name }} ``` or to merge two strings, where each string is camel cased. ``` {{ printf "%s%s" (Camel .Interface.Name) (Camel .Name) }} ``` The `printf` syntax is described in the [Go Printf Formatting](https://gobyexample.com/string-formatting). ## Debugging Templates[​](#debugging-templates "Direct link to Debugging Templates") Sometimes it is useful to see the current context of the template. You can use the `printf` function to print the current context: ``` {{ printf "%#v" . }} ``` --- # Quick Start A template is a technology template to transform the ObjectAPI in any kind and amount of other text documents, e.g. source code. It consist of a set of rules and template documents. The best way to create a new template template is to create an external template folder and link it up to an ApiGear Studio API project using an API module and a solution. The goal of this tutorial is to create a template which generates a typescript interface from an API document. For this we use an demo API which like this: ``` # demo.module.yaml schema: "apigear.module/1.0" name: demo version: "0.1" interfaces: - name: Counter properties: - name: count type: int operations: - name: increment - name: decrement ``` The API shall be transformed into a typescript interface which will finally be like this: ``` // demo.ts interface Counter { count: int; increment(): void; decrement(): void; } ``` ## Creating a template[​](#creating-a-template "Direct link to Creating a template") In this project we will create a `mytemplate` template inside a `myproject` together some apis to test the template. ``` mkdir myproject && cd myproject mkdir mytemplate && cd mytemplate ``` First we create our root project folder called `myproject` and inside our template folder called `mytemplate`. Inside the `mytemplate` folder we place a rules document (`rules.yaml`) and a templates folder for our template documents. The folder structure will look like this. ``` myproject/ mytemplate/ rules.yaml templates/ ``` The rules document defines which documents are written based on which API symbols, in our case we use the `module` scope, as we want to create one document per module. The source document is a `tpl` template document and the target document is a text document, where the target name can also be a template string. ``` # rules.yaml features: - name: default scopes: - match: module documents: - { source: module.ts.tpl, target: {{.Module.Name}}.ts } ``` The `module.ts.tpl` file inside the `mytemplate/templates` folder can be empty initially, we fill it up later. Now our basic template project is ready, it's time to link it up with an ApiGear Studio API project. ``` myproject/ mytemplate/ rules.yaml templates/ module.ts.tpl ``` ## Create API Project[​](#create-api-project "Direct link to Create API Project") Open ApiGear Studio and add an API project to the `myproject` folder. This will create an `apigear` folder to the project. From within ApiGear Studio create an API module named `demo` which will act as our test API. Copy our demo API into the API document. ``` # demo.module.yaml schema: "apigear.module/1.0" name: demo version: "0.1" interfaces: - name: Counter properties: - name: count type: int operations: - name: increment - name: decrement ``` The demo API now needs to be linked to the template rules document. This is done from within a solution document, which links API modules to templates. Open ApiGear Studio and create a solution document also called `demo`, and the content should look like this. ``` # demo.solution.yaml schema: "apigear.solution/1.0" name: demo version: "0.1" targets: - name: demo output: ../output inputs: - demo.module.yaml template: ../mytemplate features: - default ``` This will first parse all defined modules (demo) and apply the given template to the modules. The documents will then be written relative to the given output directory. Your project should look like this now: ``` myproject/ apigear/ demo.module.yaml demo.solution.yaml mytemplate/ rules.yaml templates/ module.ts.tpl ``` When you now run the solution it will create an empty `output/demo.ts` document inside the project directory. Now we have a basic setup ready. ``` myproject/ apigear/ demo.module.yaml demo.solution.yaml mytemplate/ rules.yaml templates/ module.ts.tpl output/ demo.ts ``` ## Demo Goal[​](#demo-goal "Direct link to Demo Goal") Remember we want to create an interface for each interface inside an API module. For our demo API the resulting typescript shall look like this: ``` // demo.ts interface Counter { count: int; increment(): void; decrement(): void; } ``` This defines a standalone typescript interface to be usable in your applications. The rules document already takes care that for each API module one typescript document is created, based on the module name. Now we need to fill in the content of the typescript code. ## Typescript Template[​](#typescript-template "Direct link to Typescript Template") Inside our `module.ts.tpl` template document each interface in the module shall be an typescript interface. This can be accomplished with the for-loop from the Go template engine. ``` {{ range .Module.Interfaces }} interface {{ .Name }} { } {{ end }} ``` After updating the `module.ts.tpl` with the above content, we can run the solution. This will rewrite the target document with the content of the typescript template. ``` interface Counter {} ``` ## Filling in the details[​](#filling-in-the-details "Direct link to Filling in the details") There are still the properties and operations missing from the source code. We can add them into the template using another for loop, which iterate over the properties and operations from the interface. ``` {{ range .Module.Interfaces }} interface {{ .Name }} { {{ range .Properties }} {{ .Name }}: {{ tsType . }}; {{ end }} {{ range .Operations }} {{ .Name }}(): {{ tsReturn .Return }}; {{ end }} } {{ end }} ``` This will already add the properties and some simple operations to the source code. After running the solution we will see the update source code. ``` // demo.ts interface Counter { count: int; increment(): void; decrement(): void; } ``` So great. Are we finished? The mindful reader already figured out that the operation parameters are missing. To demonstrate this we will add steps parameters to the increment and decrement operations. We first update our API demo module (`demo.module.yaml`) adding parameters to the operations like this: ``` operations: - name: increment params: - name: step type: int - name: decrement params: - name: step type: int ``` If you would run the solution again you would not see a change as we currently do not handle these parameters. We need to update the template document (`module.ts.tpl`) first to handle the parameters using the params filter. ``` {{ range .Operations }} {{ .Name }}({{ params .Params }}): {{ tsReturn .Return }}; {{ end }} ``` Now running the solution again will update the typescript source code to the final result: ``` // demo.ts interface Counter { count: int; increment(step: int): void; decrement(step: int): void; } ``` This shows how easy it is to create an own template solution for a supported technology. ## Packaging[​](#packaging "Direct link to Packaging") Template packages are git repositories, which can be published to a git server. The template package can be referenced from a solution document using the `git` scheme. ``` apigear template install ``` Or if the template is registered with the registry, it can be installed using the name. ``` apigear template install ``` ## Next Steps[​](#next-steps "Direct link to Next Steps") This simple demo shows the workflow of creating technology templates using ApiGear Studio. ApiGear support several programming languages and technologies, such as Python, C++, TypeScript, Go and others and is able to create complex solutions for almost every application. ApiGear comes with several advanced technology templates which provides solutions for the most common problems. In case the provides solutions do not fit your needs ApiGear is designed to allow quick adoptions of the underlying technology templates. --- # Introduction ApiGear Studio is a desktop application for designing APIs, generating SDKs, and monitoring API traffic — all in one integrated environment. It provides a visual interface for the complete API-first development workflow. ## Key Features[​](#key-features "Direct link to Key Features") ### API Design[​](#api-design "Direct link to API Design") Create and manage ObjectAPI module files that define your interfaces, data structures, and enums. The built-in validation ensures your API definitions are correct before code generation. ### Code Generation[​](#code-generation "Direct link to Code Generation") Configure SDK generation with solution files that link your API modules to technology templates. Run generation with a single click or enable auto-run to regenerate whenever files change. ### Scripted Backends[​](#scripted-backends "Direct link to Scripted Backends") Test your client code against simulated services. Create scenario files with JavaScript that define how your mock service behaves, then run them directly from Studio. ### Real-Time Monitoring[​](#real-time-monitoring "Direct link to Real-Time Monitoring") Connect your applications to Studio's monitoring endpoint and watch API calls flow through in real-time. Debug integration issues and verify correct API usage. ### Template Management[​](#template-management "Direct link to Template Management") Browse, install, and manage SDK templates from the ApiGear registry. Templates are cached locally and automatically versioned. ## Platform Support[​](#platform-support "Direct link to Platform Support") ApiGear Studio is available for: * **macOS** — Intel and Apple Silicon * **Windows** — 64-bit * **Linux** — 64-bit (AppImage) ## Installation[​](#installation "Direct link to Installation") Download the latest release from the [GitHub releases page](https://github.com/apigear-io/studio/releases). ### macOS[​](#macos "Direct link to macOS") 1. Download the `.dmg` file 2. Open the disk image 3. Drag ApiGear Studio to your Applications folder 4. On first launch, right-click and select "Open" to bypass Gatekeeper ### Windows[​](#windows "Direct link to Windows") 1. Download the `.exe` installer 2. Run the installer 3. Follow the installation wizard 4. Launch from the Start menu ### Linux[​](#linux "Direct link to Linux") 1. Download the `.AppImage` file 2. Make it executable: `chmod +x ApiGear-Studio-*.AppImage` 3. Run the AppImage ## Auto-Updates[​](#auto-updates "Direct link to Auto-Updates") Studio automatically checks for updates when launched. When a new version is available: 1. A notification prompts you to update 2. The update downloads in the background 3. Restart the application to apply the update Update Channels In Settings, you can choose between **stable** (recommended) and **beta** update channels. Beta releases include newer features but may be less stable. ## Configuration[​](#configuration "Direct link to Configuration") Studio stores its configuration and template cache in platform-specific locations: | Platform | Configuration Path | | -------- | ----------------------------------------------- | | macOS | `~/Library/Application Support/ApiGear Studio/` | | Windows | `%APPDATA%\ApiGear Studio\` | | Linux | `~/.config/ApiGear Studio/` | ## Server Ports[​](#server-ports "Direct link to Server Ports") Studio runs local servers for simulation and monitoring: * **Default Port**: 4333 * **Monitor Endpoint**: `http://localhost:4333/monitor/{source}` * **Simulation Endpoint**: `ws://localhost:4333/ws` You can change the port in Settings → Connection if the default conflicts with other services. ## Editor Integration[​](#editor-integration "Direct link to Editor Integration") Studio opens API files in your preferred code editor. Configure the editor command in Settings → Application: | Editor | Command | | ---------------- | --------------------------- | | VS Code | `code` | | VS Code Insiders | `code-insiders` | | Sublime Text | `subl` | | Vim | `vim` | | Custom | Any executable in your PATH | ## Getting Help[​](#getting-help "Direct link to Getting Help") * **Documentation** — Click the Help button in the header or visit [apigear.io/docs](https://apigear.io/docs) * **Issues** — Report bugs at [github.com/apigear-io/studio/issues](https://github.com/apigear-io/studio/issues) * **Community** — Join the discussion on [Slack](https://join.slack.com/t/apigear/shared_invite/zt-3mjx0cl9y-8zWn~ufJnzXCl5aaPjXEfg) --- # Project Management ApiGear Studio organizes your work into projects. A project is a folder containing an `apigear` subfolder where all API definitions, solutions, and scenarios are stored. ## Welcome Screen[​](#welcome-screen "Direct link to Welcome Screen") When you launch Studio, the Welcome screen provides three ways to get started: ### Create New Project[​](#create-new-project "Direct link to Create New Project") Start a fresh API project: 1. Click **Create Project** 2. Select or create a folder for your project 3. Studio creates an `apigear` subfolder for your documents 4. The project opens in the workspace The folder you select becomes your project root. All generated code paths in solutions are relative to this location. ### Open Existing Project[​](#open-existing-project "Direct link to Open Existing Project") Open a project you've worked on before: 1. Click **Open Project** 2. Browse to your project folder (the one containing the `apigear` subfolder) 3. Studio loads all documents from the `apigear` folder ### Recent Projects[​](#recent-projects "Direct link to Recent Projects") Quick access to projects you've recently opened: * Click any project path to open it immediately * Click **Delete** to remove a project from the recent list (this doesn't delete the actual files) ## Project Structure[​](#project-structure "Direct link to Project Structure") A typical ApiGear project has this structure: ``` my-project/ ├── apigear/ │ ├── myapi.module.yaml # API definitions │ ├── myapi.solution.yaml # Generation config │ └── myapi.scenario.yaml # Simulation scripts ├── generated-cpp/ # Generated SDK (example) └── generated-python/ # Generated SDK (example) ``` The `apigear` folder contains: | File Type | Extension | Purpose | | --------- | ---------------- | ----------------------------- | | Module | `.module.yaml` | API interface definitions | | Solution | `.solution.yaml` | Code generation configuration | | Scenario | `.scenario.yaml` | Simulation scripts | ## Dashboard View[​](#dashboard-view "Direct link to Dashboard View") Once a project is open, the Dashboard shows all documents at a glance: * **Document list** — All modules, solutions, and scenarios in your project * **Check All** — Validate all documents with one click * **Document icons** — Visual indication of document type Each document entry shows: * Document name and type icon * Open button to edit in your code editor * Validation status * Action menu with additional options ## Header Actions[​](#header-actions "Direct link to Header Actions") The header bar provides quick project actions: | Button | Action | | ------------------ | -------------------------------------- | | **New Document** | Create a module, solution, or scenario | | **Switch Project** | Return to the Welcome screen | | **Open Folder** | Open project folder in file explorer | | **Reload** | Refresh documents from disk | | **Help** | Open documentation | ### Creating Documents[​](#creating-documents "Direct link to Creating Documents") Click **New Document** to create: 1. **Module** — An API definition file * Define interfaces with properties, methods, and signals * Define data structures and enumerations * Use ObjectAPI YAML format 2. **Solution** — A code generation configuration * Link modules to SDK templates * Configure output paths * Enable template features 3. **Scenario** — A simulation script * JavaScript code for mock service behavior * Define property values and method responses * Script complex interaction sequences ## Navigation Sidebar[​](#navigation-sidebar "Direct link to Navigation Sidebar") The sidebar provides access to specialized views: | View | Purpose | | -------------- | ---------------------------------------- | | **Dashboard** | Overview of all project documents | | **Modules** | Filter to show only API modules | | **Solutions** | Manage code generation with run controls | | **Simulation** | Run and manage simulation scenarios | | **Monitor** | View real-time API events | | **Templates** | Install and manage SDK templates | | **Logs** | Application event history | | **Settings** | Configure Studio behavior | ## Working with Multiple Projects[​](#working-with-multiple-projects "Direct link to Working with Multiple Projects") Studio works with one project at a time. To switch projects: 1. Click **Switch Project** in the header 2. You'll return to the Welcome screen 3. Select a different project to open Your recent projects list makes it easy to jump between projects you're actively developing. ## Project Best Practices[​](#project-best-practices "Direct link to Project Best Practices") ### Organize by Domain[​](#organize-by-domain "Direct link to Organize by Domain") For larger systems, create separate modules for different domains: ``` apigear/ ├── user.module.yaml # User management APIs ├── inventory.module.yaml # Inventory APIs ├── orders.module.yaml # Order processing APIs └── app.solution.yaml # Single solution for all modules ``` ### Version Control[​](#version-control "Direct link to Version Control") Keep your `apigear` folder in version control: * Track API changes over time * Collaborate with team members * Review API changes in pull requests Generated code can be committed or gitignored depending on your workflow. ### Separate Solutions[​](#separate-solutions "Direct link to Separate Solutions") Create different solutions for different targets: ``` apigear/ ├── api.module.yaml ├── cpp.solution.yaml # C++ SDK generation ├── python.solution.yaml # Python SDK generation └── unreal.solution.yaml # Unreal Engine generation ``` This lets you generate SDKs for multiple platforms from the same API definitions. --- # Quick Start This guide walks you through the main workflows in ApiGear Studio, from creating your first project to generating code and monitoring APIs. ## Welcome Screen[​](#welcome-screen "Direct link to Welcome Screen") When you launch ApiGear Studio, you'll see the Welcome screen with three main options: * **Create New Project** — Start a fresh project in a new folder * **Open Existing Project** — Browse to an existing ApiGear project folder * **Recent Projects** — Quick access to recently opened projects Select a folder for your project. Studio will create an `apigear` subfolder to store your API definitions and solution files. ## Project Workspace[​](#project-workspace "Direct link to Project Workspace") Once a project is open, you'll see the main workspace with a navigation sidebar on the left. The sidebar provides access to all major features: | Icon | View | Description | | ---- | --------------------- | --------------------------------- | | 📊 | **Dashboard** | Overview of all project documents | | 📦 | **Modules** | API module definitions | | ⚙️ | **Solutions** | Code generation configurations | | ▶️ | **Simulation** | Scripted backend scenarios | | 📋 | **Simulation Events** | Detailed simulation event log | | 📡 | **Monitor** | Real-time API event monitoring | | 📝 | **Logs** | Application event history | | 🧩 | **Templates** | SDK template management | | ⚙️ | **Settings** | Application configuration | ## Creating Documents[​](#creating-documents "Direct link to Creating Documents") Click the **New Document** button in the header to create: * **Module** — An API definition file (`.module.yaml`) * **Solution** — A code generation configuration (`.solution.yaml`) * **Scenario** — A simulation script (`.scenario.yaml`) Enter a name and the document will be created in your project's `apigear` folder. ## Working with Modules[​](#working-with-modules "Direct link to Working with Modules") The **Modules** view shows all API module files in your project. For each module, you can: * **Open** — Click the edit icon to open in your code editor * **Validate** — Click the check icon to verify the module syntax * **Copy Path** — Copy the file path via the menu (⋮) Modules define your API surface using the ObjectAPI format with interfaces, properties, methods, signals, structs, and enums. ## Code Generation[​](#code-generation "Direct link to Code Generation") The **Solutions** view manages your code generation configurations. ### Running Generation[​](#running-generation "Direct link to Running Generation") 1. Create a solution file that links modules to a template 2. Click the **Run** button (▶️) next to the solution 3. Watch the generation progress in the event drawer 4. Generated code appears in the configured output folder ### Auto-Run Mode[​](#auto-run-mode "Direct link to Auto-Run Mode") Enable **Auto Run** on a solution to automatically regenerate code when the solution file changes: 1. Click the menu (⋮) on a solution 2. Select **Auto Run** 3. The solution will regenerate whenever you save changes 4. Click **Stop** to disable auto-run ## Managing Templates[​](#managing-templates "Direct link to Managing Templates") The **Templates** view has two tabs: ### Installed Tab[​](#installed-tab "Direct link to Installed Tab") Shows templates in your local cache: * View template information * Remove templates you no longer need ### Available Tab[​](#available-tab "Direct link to Available Tab") Browse templates from the ApiGear registry: * Click **Refresh** to update the template list * Click **Install** on a template to download it * Select a specific version if needed Templates are downloaded to your local cache and referenced by name in solution files. ## Running Simulations[​](#running-simulations "Direct link to Running Simulations") The **Simulation** view manages scripted backend scenarios. ### Starting a Simulation[​](#starting-a-simulation "Direct link to Starting a Simulation") 1. Create a scenario file with simulation logic 2. Click the **Play** button (▶️) next to the scenario 3. The simulation server starts and executes your script 4. View events in the side drawer or navigate to **Simulation Events** ### Simulation Events[​](#simulation-events "Direct link to Simulation Events") The **Simulation Events** view shows a detailed log of all simulation activity: * Property changes * Method calls * Signal emissions * Timing information ## Real-Time Monitoring[​](#real-time-monitoring "Direct link to Real-Time Monitoring") The **Monitor** view displays live API events from connected applications. Events flow in real-time as your applications make API calls. The monitor shows: * Event type (property, method, signal) * Interface and member names * Parameters and values * Timestamps Use monitoring to debug API interactions and verify correct behavior. ## Application Logs[​](#application-logs "Direct link to Application Logs") The **Logs** view shows application-level events and history: * Code generation results * Simulation start/stop events * Validation results * Error messages ## Settings[​](#settings "Direct link to Settings") The **Settings** view configures Studio behavior: ### Connection Tab[​](#connection-tab "Direct link to Connection Tab") * **Server Port** — Port for simulation and monitoring servers * Shows the addresses for Monitor and Simulation connections ### Application Tab[​](#application-tab "Direct link to Application Tab") * **Update Channel** — Choose stable or beta updates * **Editor Command** — Configure your preferred code editor ## Header Actions[​](#header-actions "Direct link to Header Actions") The header bar provides quick access to common actions: * **New Document** — Create modules, solutions, or scenarios * **Switch Project** — Return to the welcome screen * **Open Folder** — Open the project folder in your file explorer * **Reload** — Refresh the project from disk * **Help** — Open the documentation ## Typical Workflow[​](#typical-workflow "Direct link to Typical Workflow") 1. **Create a project** — Start with a new or existing folder 2. **Define your API** — Create modules describing your interfaces 3. **Install templates** — Get the SDK templates you need 4. **Configure generation** — Create a solution linking modules to templates 5. **Generate code** — Run the solution to produce SDK code 6. **Test with simulation** — Create scenarios to mock your service 7. **Monitor in production** — Watch real-time API events Each step builds on the previous, taking you from API design to production-ready code. ---