Designing Plugins
Design lifecycle-safe, SDK-only Turboism plugins that fail closed on unavailable capabilities.
A durable Turboism plugin has one user-visible purpose, depends on the SDK only, and lets Runtime own host integration, scheduling, safety, and cleanup.
Keep the boundary small
Plugin -> SDK -> Runtime policy -> versioned Adapter/Provider -> Cubism/EditorDo not add plugin-local abstractions for Runtime concerns. Reuse PluginContext services for configuration, storage, tasks, events, UI, user files, and Cubism access.
Avoid:
- direct Runtime or
com.live2d.*dependencies; - reflection and raw host objects;
- unmanaged executors, threads, and timers;
- Swing/AWT host-widget traversal;
- global mutable registries;
- speculative factories and extension layers.
Plan the lifecycle
Use the four actual lifecycle methods:
init— retain the context, register schemas, and build plugin-private state.enable— register actions, events, UI, and tasks.disable— stop active behavior without assuming hot unload.shutdown— release plugin-private state after Runtime lifecycle settlement.
Put every closeable registration or handle in DisposableScope unless it is intentionally closed earlier.
Separate permission from availability
A permission answers whether the plugin may cross a risk boundary. A capability answers whether Runtime and the active host version can perform the operation.
Design both paths:
- success through the supported Provider;
- explicit unavailable, unsupported, stale, denied, timeout, backpressure, or rejected outcomes.
Never treat a permission as proof that a Provider exists.
Design writes around Editor ownership
For an Editor-attached model, Editor authoring state is the write source of truth. A safe write may require:
- validation;
- host-thread dispatch;
- transaction and Undo;
- dirty-state and refresh handling;
- rollback or explicit partial failure;
- generation/stale-target rejection;
- save/reopen verification when persistence is claimed.
Do not mutate Cubism Core as a second independent authoring state or synthesize an Undo entry around an evaluation-only mutation.
Use immutable values at boundaries
Events, UI descriptors, snapshots, selections, configuration values, and task requests should be immutable SDK- or plugin-owned values.
Do not leak:
- host objects;
- host UI controls;
- native handles;
- host ClassLoaders;
- mutable Cubism-owned arrays;
- unrestricted filesystem paths.
Prefer one action, one contribution
Register user behavior as an Action, then bind menu, toolbar, panel, or context-menu contributions to its action ID. This keeps behavior testable without requiring the host UI.
Test the smallest real contract
A plugin with only lifecycle logging needs a constructor/lifecycle test. A plugin with registrations should use a recording/fake PluginContext and verify:
- requested permissions and unavailable paths;
- registration and scope cleanup;
- disable/shutdown behavior;
- immutable event and DTO behavior;
- configuration migrations and revision conflicts;
- task cancellation or rejection where applicable.
Host-version claims require the project's higher-risk validation workflow; fake tests do not establish real-host readiness.
Design checklist
- The plugin has one clear purpose.
- Production code depends only on the SDK.
- Manifest v2 declares exact entrypoints, resources, dependencies, and minimum permissions.
- Registrations and handles are closeable and scoped.
- Unavailable Providers fail closed with useful diagnostics.
- Configuration, storage, and user files use the correct service.
- No unmanaged thread, executor, timer, host object, or reflection bridge exists.
- Writes preserve Editor ownership and Undo semantics.
- The smallest focused test exercises the actual contract.