Skip to main content
Docs: template-unreal

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 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 clients instead of stubs. Both implement the same interface, so you can swap implementations without changing your application code.

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​

Files IoWorldHello.h and IoWorldHello.cpp contain your implementation of the IIoWorldHelloInterface. The implementation class inherits from UAbstractIoWorldHello (see api) which provides property storage, Publisher for notifications, and subsystem lifecycle.

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<UIoWorldHelloImplementation>()
  • Blueprint-friendly: Accessible through "Get Game Instance Subsystem" node

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 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​

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<int32> 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<int32> UIoWorldHelloImplementation::SayAsync(const FIoWorldMessage& Msg, EIoWorldWhen When)
{
// Custom async implementation
TPromise<int32> Promise;
TFuture<int32> 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​

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​

Module Dependency​

Add the Implementation module to your .Build.cs:

PublicDependencyModuleNames.AddRange(new string[] { "IoWorldImplementation" });

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++​

#include "IoWorld/Implementation/IoWorldHello.h"

// From any UObject with access to the world
UIoWorldHelloImplementation* Hello = GetGameInstance()->GetSubsystem<UIoWorldHelloImplementation>();
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<IIoWorldHelloInterface> Hello =
GetGameInstance()->GetSubsystem<UIoWorldHelloImplementation>();

// Now use through the interface
Hello->SetLast(Msg);

Tests​

The stubs feature generates test fixtures for each interface.

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​

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​

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​

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​

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​

Override Deinitialize to clean up resources:

void UIoWorldHelloImplementation::Deinitialize()
{
// Clean up connections, timers, etc.

Super::Deinitialize();
}