Skip to content

PlatformModule

A PlatformModule gives a platform SDK capability a typed C++ interface. It does not have to inherit a framework class or route every call through string messages.

Define the shared interface, implement it in platform-selected source files, and register a factory in a root hook. The complete declaration below leaves CreateDeviceInfo to the platform implementation rather than pretending to return a real platform value:

#include <huxerui/huxerui.h>
#include <memory>
#include <string>
using namespace huxerui;
struct DeviceInfo {
virtual ~DeviceInfo() = default;
virtual std::string Model() const = 0;
};
std::shared_ptr<DeviceInfo> CreateDeviceInfo(PlatformAdapter& adapter);
void InstallDeviceInfo(RootContext& root) {
root.RegisterPlatformModule<std::shared_ptr<DeviceInfo>>("app/device-info", CreateDeviceInfo);
root.Provide<DeviceInfo>(root.OpenPlatformModule<std::shared_ptr<DeviceInfo>>("app/device-info"));
}
View App() {
auto device = UseService<DeviceInfo>();
return Text(device->Model());
}
AppOptions Options() {
AppOptions options;
options.root_hooks.push_back(InstallDeviceInfo);
return options;
}
const Application application{App, Options()};

The service is shared for this Runtime/window, not a process-wide singleton. Keep platform handles in the implementation, respect its UI-thread and resource-lifetime rules, and report unavailable capabilities explicitly.

Use the free OpenPlatformModule<Module>() only inside committed Lifecycle setup when an instance should follow a component’s mount/dependency lifetime. Return a cleanup that closes or releases it. Opening during composition is rejected: composition may be retried and is not a safe place to create platform side effects.

Factories may accept an explicit options type through RegisterPlatformModule<Module, Options>. The open call must use the same registered types and case-sensitive name. Prefer an integration library’s Install... and Use... helpers so applications do not repeat registration names.

Java or JavaScript objects may need asynchronous named methods and events. Wrap PlatformChannel in the same typed service interface rather than exposing payload parsing throughout application code.

channel.Invoke<double>("batteryLevel", std::monostate{},
[level](PlatformResult<double> result) {
if (const auto* value = std::get_if<double>(&result)) {
level = *value;
}
});

The result type is explicit; primitive wire conversion is built in. Structured request/result types own static Encode and Decode operations. An event key supplies its Event<...> signature and stable Name; its structured value type owns conversion.

Keep the returned request identity if you need to cancel. Closing a channel affects its copies, invalidates pending work, detaches events, and disposes the platform instance. Completion is delivered at most once on the owning UI thread. Do not wait synchronously for it or assume that cancelled work still calls your completion.

PlatformPayload can carry std::shared_ptr<ExternalTexture> and FileReference alongside ordinary values. Forward the complete encoded envelope, including its capability tables. Do not reduce a document-provider reference to a filesystem path or a live texture to an integer handle.

  • Use ordinary C++ for shared algorithms.
  • Use a root service for coordinated per-window ownership.
  • Register a PlatformModule for a platform implementation of that service.
  • Use PlatformView for an actual platform control.
  • Use ExternalTexture for changing pixels without platform-control input.

Test missing registration, exact options types, teardown during work, cancellation, late events, and thread delivery. See the registry and channel reference for individual contracts.

Use BufferReference in a typed PlatformPayload for large CPU data that can share an address-stable lifetime. It is not an immutable snapshot: wait for readers before reusing producer storage, and publish frame sequence separately from buffer identity. Bytes remains the correct choice for serialized or durable data.

Android uses HuxerUIBufferReference.wrap with a direct ByteBuffer. The current position-to-limit range is retained, and withBuffer exposes a read-only view only for that synchronous callback. Close wrappers you own; payloads and slices keep independent shares. A final-release callback is not a frame-completion signal and may run on any final releasing thread.

Apple exposes HUXBufferReference (Swift BufferReference) with a retained owner and scoped unsafe-byte access. Windows/Linux C++ factories can receive BufferReference directly. Web payload bridges reject it; do not simulate zero-copy by encoding a large buffer to JSON.

See BufferReference for construction, slicing and equality, and the buffer analysis source for a real platform producer.