# ECommons education for Dalamud plugin authors

Updated: 2026-08-17

Audience: Dalamud plugin authors, maintainers, reviewers, and software agents.

ECommons is a broad utility library for Dalamud plugins. It wraps recurring work such as service access, frame-driven task queues, addon callbacks, UI readers and controllers, throttling, configuration, events, hooks, logging, reflection, and IPC registration.

This is an educational map, not a replacement for the source. API examples were checked against ECommons commit `e6be8f0fd7786a9e1781db2e71cb5b9146f04980` on 2026-08-17. That commit was dated 2026-08-08 and its project file carried base version `3.2.1.17` targeting `net10.0-windows7.0`. Refresh the repository, package metadata, Dalamud API level, and game structures before implementation.

## Start with the right mental model

ECommons is not one feature. It is a toolbox layered over Dalamud services and FFXIVClientStructs.

Use it when a maintained helper expresses the contract you need more clearly than custom low-level code. Do not adopt a helper merely to shorten a line: compare lifecycle ownership, error behavior, patch sensitivity, dependency cost, and how well the helper fits the plugin's existing architecture.

| Need | Useful ECommons surface |
| --- | --- |
| Access injected Dalamud services from multiple classes | `Svc` |
| Run ordered nonblocking work on framework updates | `NeoTaskManager.TaskManager` |
| Fire addon callbacks with converted `AtkValue` arguments | `Automation.Callback` |
| Interact with a known addon through a semantic wrapper | `AddonMaster` |
| Read structured addon values | `AtkReader` and reader implementations |
| Send validated chat or slash commands | `Automation.Chat` |
| Rate-limit time- or frame-based actions | `EzThrottler`, `FrameThrottler` |
| Simplify configuration and windows | `EzConfig`, `EzConfigGui` |
| Manage event or hook lifecycles | `EzEvent`, `EzHook` family |
| Register or consume IPC declaratively | `EzIPC` |
| Discover another loaded plugin for a last-resort adapter | `DalamudReflector` plus `ReflectionHelper` |

## Initialization and disposal

Initialize ECommons once from the plugin entry point and dispose it once during plugin teardown.

```csharp
using Dalamud.Plugin;
using ECommons;

public sealed class Plugin : IDalamudPlugin
{
    public Plugin(IDalamudPluginInterface pluginInterface)
    {
        ECommonsMain.Init(pluginInterface, this);
    }

    public void Dispose()
    {
        ECommonsMain.Dispose();
    }
}
```

At the observed snapshot, `Init` accepts an `IDalamudPlugin` or `IAsyncDalamudPlugin` instance plus optional advanced modules. `Dispose` performs broad cleanup across ECommons-managed task managers, hooks, IPC, shared data, event handlers, config/window helpers, throttlers, DTR entries, and other registered components.

The broad cleanup is useful, but ownership should still be obvious in consumer code. Dispose consumer-owned objects, subscriptions, windows, and cancellation sources explicitly when they have their own lifecycle.

## Advanced module selection

The observed `Module` enum contains exactly:

| Module | Purpose | Cost or caution |
| --- | --- | --- |
| `All` | Requests all advanced modules | Highest surface area; avoid as a default |
| `DalamudReflector` | Reflects over internal Dalamud/plugin state | Private contracts plus framework monitoring |
| `ObjectFunctions` | Object-related game functions | Hooks patch-sensitive game functions |
| `ObjectLife` | Object lifecycle tracking | Adds update/hook work and state |
| `SplatoonAPI` | Splatoon integration | Optional cross-plugin dependency behavior |
| `VfxTracking` | VFX lifecycle tracking | Hooks patch-sensitive game functions |

Example with only reflection enabled:

```csharp
ECommonsMain.Init(pluginInterface, this, Module.DalamudReflector);
```

Base initialization already enables many utility surfaces. Request advanced modules only when a feature requires them; do not pass `Module.All` as cargo cult.

## Dependency and version policy

Pin a reviewed package version in the project and update it intentionally. The exact package declaration depends on the plugin's current build template and package feed, so verify the ECommons repository and current Dalamud project guidance rather than copying an old version line.

For every update:

1. inspect ECommons source changes between the pinned and proposed versions;
2. confirm the target framework and Dalamud API match the plugin;
3. rebuild from a clean dependency restore;
4. run unit/static tests for wrappers and task logic;
5. load the plugin in the supported Dalamud track;
6. exercise addon, hook, reflection, and IPC surfaces in game; and
7. recheck dispose/reload paths.

Build success does not prove game structures, addon callback values, hooks, or reflected private paths at runtime.

## Source-area map

ECommons is organized into focused areas. The exact file list changes; these are the durable concepts:

- **Automation** - Neo and legacy task managers, callbacks, chat, commands, macros, key input, and cutscene helpers.
- **DalamudServices** - the injected `Svc` service holder.
- **UIHelpers** - addon discovery, semantic AddonMaster wrappers, and AtkReader implementations.
- **GameHelpers** - higher-level player and game-state access.
- **Configuration / SimpleGui** - configuration storage and window bootstrap helpers.
- **Throttlers / Schedulers** - time-, frame-, and tick-based pacing.
- **Events / EzEventManager** - login, territory, framework, addon, and other event helpers.
- **EzHookManager / Hooks** - signature and game-hook lifecycle helpers.
- **EzIpcManager / EzSharedDataManager** - cross-plugin calls/events and shared data.
- **Reflection** - Dalamud/plugin discovery and field/property/method helpers.
- **ExcelServices / LazyDataHelpers** - Lumina data access and typed convenience APIs.
- **GenericHelpers / CSExtensions / StringHelpers** - reusable safety, collection, pointer, string, and game helpers.
- **ImGuiMethods / EzDTR / Logging** - presentation, DTR bar, notifications, and logs.
- **CircularBuffers / MathHelpers / Numeric** - general data and math utilities.
- **Interop / Networking / GameFunctions** - lower-level, platform- or patch-sensitive operations.

## `Svc`: centralized Dalamud services

`ECommons.DalamudServices.Svc` centralizes services initialized from the plugin interface. Typical categories include framework/update access, client state, condition flags, object tables, game data, plugin logging, commands, IPC, textures, addon lifecycle, key state, and signature scanning.

```csharp
using ECommons.DalamudServices;

if (Svc.ClientState.IsLoggedIn)
{
    var territory = Svc.ClientState.TerritoryType;
    Svc.Log.Debug("Current territory: {Territory}", territory);
}
```

Benefits:

- less constructor plumbing for small helpers;
- one familiar access pattern across ECommons-consuming plugins; and
- initialization handled by `ECommonsMain.Init`.

Tradeoffs:

- global access can hide dependencies;
- tests may be harder when code reaches static services directly; and
- unavailable game state still requires null/readiness checks.

For test-heavy domain code, pass narrow interfaces into services and keep `Svc` at the Dalamud adapter boundary.

## NeoTaskManager: nonblocking ordered work

`ECommons.Automation.NeoTaskManager.TaskManager` runs tasks from `Framework.Update`. It is a state queue, not a background thread.

At the observed snapshot, task functions use these results:

| Result | Meaning |
| --- | --- |
| `false` | Current task is not complete; poll again on a later update |
| `true` | Current task completed; advance to the next task |
| `null` | Abort the queue |

Action overloads run once and complete. Default task timeout is 30,000 ms, with abort-on-timeout and abort-on-error enabled in the constructed default configuration.

### Create and enqueue

```csharp
using ECommons.Automation.NeoTaskManager;

private readonly TaskManager tasks = new(new TaskManagerConfiguration
{
    TimeLimitMS = 30_000,
    AbortOnTimeout = true,
    AbortOnError = true,
    TimeoutSilently = false,
    ShowDebug = false,
    ShowError = true,
});

public void QueueExample()
{
    tasks.Enqueue(
        () => IssueCommand(),
        "Issue command");

    tasks.Enqueue(
        () => IsTransitionComplete(),
        "Wait for transition",
        new TaskManagerConfiguration(timeLimitMS: 45_000));
}
```

An `Action` task should issue a short operation and return immediately. A predicate task observes state on later frames. Never block the framework callback with `Thread.Sleep`, synchronous I/O, or a busy loop.

### Operations and state

Representative surfaces include:

```csharp
tasks.IsBusy;
tasks.NumQueuedTasks;
tasks.Progress;
tasks.CurrentTask;
tasks.RemainingTimeMS;
tasks.Abort();
tasks.AbortCurrent();
```

The manager also supports delays, inserted front-of-queue tasks, stacked sequences, and a step mode for controlled debugging. Those features are powerful; document queue ordering so an inserted or stacked action cannot violate workflow invariants.

### Timeout and exception events

`TaskManagerConfiguration` exposes hooks for timeout, completion, exceptions, and a companion action:

```csharp
var configuration = new TaskManagerConfiguration
{
    OnTaskTimeout = (task, ref long remainingTimeMs) =>
    {
        // Extend only when new evidence justifies more time.
    },
    OnTaskCompletion = (task, ref bool? result) =>
    {
        // Observe or deliberately transform completion.
    },
    OnTaskException = (task, exception, ref bool continueTask, ref bool? abort) =>
    {
        // Log bounded context and choose an explicit failure policy.
    },
    CompanionAction = task =>
    {
        // Short per-frame monitoring only.
    },
};
```

Avoid extending timeouts forever. A task must complete, abort, be cancelled, or report a bounded failure. Keep destructive operations idempotent before retrying them.

### Task design checklist

- Stable task name appears in UI, logs, and tests.
- Enter action runs once, not on every poll.
- Completion predicate handles temporary unavailable state.
- Timeout is finite and specific to the operation.
- Retry is allowed only when repeating is safe.
- Cancellation and plugin disposal clear work.
- Relog, teleport, and zoning use readiness gates, not delays alone.
- The queue exposes current step, elapsed time, and last failure.

## Callback: typed addon callback arguments

`ECommons.Automation.Callback.Fire` converts common managed values into `AtkValue` entries and invokes an addon callback.

```csharp
using ECommons.Automation;

// Examples only: callback indexes and values are addon/version specific.
Callback.Fire(addon, updateState: false, 0);
Callback.Fire(addon, updateState: true, 3, 1);
Callback.Fire(addon, updateState: false, "text");
```

The observed implementation handles values including `int`, `uint`, `float`, `bool`, `string`, and an existing `AtkValue`. It also exposes raw callback and development-time callback-decoding/hook helpers.

Important boundaries:

- The helper converts memory safely; it cannot tell you whether a callback index or value is semantically correct.
- Discover callback contracts from current source, structures, and controlled observation.
- Validate addon pointer, visibility, readiness, and expected state first.
- Avoid leaving a broad callback logging hook active outside deliberate debugging.

Prefer a semantic `AddonMaster` method when one exists because it communicates intent better than raw callback numbers.

## Chat and command execution

`ECommons.Automation.Chat` wraps chat-box command handling and UTF-8 conversion.

```csharp
using ECommons.Automation;

Chat.SendMessage("A validated chat message");
Chat.ExecuteCommand("/example");

var safe = Chat.SanitiseText(untrustedText);
```

The observed `SendMessage` path rejects messages beyond its byte limit and text that changes during sanitization. `SendMessageUnsafe` bypasses those protections and should be reserved for already-validated internal bytes.

Do not build command strings from untrusted input without allow-listing values. Sending a command is only the start of a workflow; observe the resulting state and use timeouts.

## Windows key input

`ECommons.Automation.WindowsKeypress` wraps Win32 key-down/up operations and, in its Forms companion, `System.Windows.Forms.Keys` values.

```csharp
WindowsKeypress.SendKeypress(Keys.Escape);
WindowsKeypress.SendKeyDown(Keys.W);
WindowsKeypress.SendKeyUp(Keys.W);
```

Key simulation is platform-specific and stateful. Always release held keys on cancellation, error, dispose, and focus loss. Prefer game/addon APIs when available.

## AddonMaster: semantic addon interaction

AddonMaster implementations wrap known game addons with meaningful properties and actions. The observed source tree contains well over one hundred implementations spanning dialogs, inventory, duties, crafting, gathering, retainers, shops, travel, social UI, portraits, island/society content, and more.

Representative groups include:

- **Dialogs:** `SelectYesno`, `SelectString`, `SelectIconString`, `SelectOk`, `InputNumeric`, `Talk`, `Dialogue`.
- **Inventory and items:** `Inventory`, `InventoryLarge`, `InventoryExpansion`, `ItemFinder`, `ItemSearchResult`, `ItemInspectionResult`.
- **Retainers:** `RetainerList`, `RetainerSell`, `RetainerTaskAsk`, `RetainerTaskResult`, item-transfer views.
- **Duty and party:** `ContentsFinderConfirm`, `ContentsFinderSetting`, `ContentsFinderStatus`, `LookingForGroup` surfaces.
- **Crafting and gathering:** `Gathering`, `GatheringMasterpiece`, `RecipeNote`, `Synthesis`, `SynthesisSimpleDialog`.
- **Shops and currency:** `Shop`, `ShopCardDialog`, `ShopExchangeCurrency`, exchange confirmation dialogs.
- **Travel and world UI:** `Teleport`, world-travel and title-menu surfaces.

Conceptual usage:

```csharp
using ECommons.UIHelpers.AddonMasterImplementations;

if (GenericHelpers.TryGetAddonByName<AddonSelectYesno>("SelectYesno", out var addon))
{
    new AddonMaster.SelectYesno(&addon->AtkUnitBase).Yes();
}
```

Exact pointer types and constructors change with generated structures. Confirm the current implementation before copying code.

AddonMaster improves readability and centralizes known callback contracts. It does not remove the need to check that the correct dialog is open. Validate prompt text or upstream workflow state before clicking a destructive confirmation.

## AtkReader: structured UI reading

AtkReader and its implementations turn addon values into higher-level records for known UI surfaces. The observed tree includes readers for contexts such as select strings, retainers, synthesis, letters, banners, and other specialized addons.

Use a reader when:

- the addon exposes structured values that are otherwise easy to index incorrectly;
- a matching reader exists for the current addon layout; and
- the workflow benefits from separating “read state” from “perform action.”

Treat reader output as a snapshot. Addon memory can be reconstructed between frames. Never retain unmanaged pointers beyond their valid callback/update scope.

## `Player` and game-state helpers

`ECommons.GameHelpers.Player` provides higher-level access to local-player availability and commonly needed state. It can reduce repeated object-table and null-check boilerplate.

Use the helper as a readiness input, not a proof that a complete workflow is ready. For example, a local player may exist while the territory, addon, condition flags, or external dependency required by the next action is not ready.

A robust gate combines the exact conditions needed by the next step and requires consecutive successful observations after disruptive transitions.

## Time and frame throttling

`EzThrottler` gates actions by elapsed time; `FrameThrottler` gates by framework frames.

```csharp
using ECommons.Throttlers;

if (EzThrottler.Throttle("example-refresh", 1_000))
{
    RefreshSnapshot();
}

if (FrameThrottler.Throttle("example-frame-check", 30))
{
    CheckState();
}

EzThrottler.Reset("example-refresh");
```

Use stable, namespaced keys. A throttle prevents frequency; it does not provide mutual exclusion, cancellation, readiness, or idempotency. Do not use `reThrottle` without understanding how repeated calls move the next allowed time.

## Configuration with EzConfig

`ECommons.Configuration.EzConfig` provides configuration load/save support and participates in ECommons disposal-time saving.

```csharp
using ECommons.Configuration;

EzConfig.Init();
var config = EzConfig.LoadConfiguration<PluginConfiguration>("configuration.json");
EzConfig.Save();
```

Before adopting it, decide:

- who owns the config instance;
- where and when migrations run;
- whether multiple files are necessary;
- what happens after malformed JSON;
- whether saving on dispose is enough; and
- how secrets or account-linked data are excluded or protected.

Do not silently discard a user's configuration after a deserialization or migration failure. Preserve recoverable evidence and fail safely.

## Windows with EzConfigGui

`ECommons.SimpleGui.EzConfigGui` can initialize a config/main window, register drawing and open events, toggle it, and save associated configuration when the managed window closes.

It is useful for a small plugin with a conventional window lifecycle. A plugin with several independently owned windows, complex docking, or testable presentation boundaries may prefer an explicit Dalamud `WindowSystem`.

Choose on lifecycle clarity, not line count.

## Events and lifecycle helpers

### ProperOnLogin

`ProperOnLogin` is intended for work that must wait beyond the earliest login signal until usable game state exists.

```csharp
using ECommons.Events;

ProperOnLogin.Register(() =>
{
    // Rebuild state that requires a fully available player.
});
```

Still make the callback idempotent and recheck the exact state it needs. Login and plugin reload sequences can interleave.

### EzEventManager

The `EzEvent` family centralizes registration and disposal for framework, territory, logout, addon lifecycle, and related events. Prefer it when its ownership model matches the service. Keep handlers short and queue longer work.

### EzHookManager

The EzHook family reduces signature/hook boilerplate and participates in centralized disposal. Hooks remain patch-sensitive native boundaries. Every hook needs:

- a source-backed current signature or generated interop target;
- an original delegate path;
- exception containment;
- thread/context awareness;
- a deterministic enable/disable lifecycle; and
- runtime testing on the supported game/Dalamud version.

Automatic disposal is useful but does not make a stale signature safe.

## EzIPC: declarative IPC

`ECommons.EzIpcManager` uses attributes to register provider methods/events and populate subscriber delegates. It is built on Dalamud IPC concepts, so tags and signatures still form the public contract.

Provider sketch from the observed ECommons documentation:

```csharp
using ECommons.EzIpcManager;

public sealed class PublicApi
{
    public PublicApi() => EzIPC.Init(this);

    [EzIPC]
    public void Refresh() => QueueRefresh();

    [EzIPC("GetState")]
    public string ReadState() => "ready";

    [EzIPCEvent]
    public Action<string>? StateChanged;
}
```

Subscriber sketch:

```csharp
public sealed class PublicApiClient
{
    public PublicApiClient() => EzIPC.Init(this, "ExamplePlugin");

    [EzIPC]
    public readonly Action Refresh = null!;

    [EzIPC("GetState")]
    public readonly Func<string> ReadState = null!;

    [EzIPCEvent]
    private void StateChanged(string state)
    {
        // Return quickly; enqueue long work.
    }
}
```

Use `applyPrefix: false` for a fully specified tag only when integrating with a provider that uses another naming convention. Document the full public tag, signature, version, and absence behavior.

For a detailed provider/subscriber design and IPC-versus-reflection decision matrix, read [Reflection and IPC](./reflection-and-ipc.html).

## Reflection helpers

The `Module.DalamudReflector` module discovers live plugin instances and monitors installed-plugin changes. `ReflectionHelper` exposes common operations such as:

```csharp
var value = instance.GetFoP("MemberName");
instance.SetFoP("MemberName", newValue);
var result = instance.Call("MethodName", arguments);
```

Reflection reaches private contracts. Use it only after public API and IPC options are exhausted, pin the observed target contract, validate types, read back writes, invalidate caches on reload, and fail the optional feature cleanly.

## GenericHelpers

`GenericHelpers` is a large utility area. Common categories include:

- safe execution wrappers;
- addon lookup;
- occupied/busy state helpers;
- collection operations;
- string and whitespace helpers;
- approximate vector comparisons;
- key-state checks; and
- small pointer/game conveniences.

```csharp
using static ECommons.GenericHelpers;

if (!IsOccupied() && TryGetAddonByName<AtkUnitBase>("SelectString", out var addon))
{
    // Validate expected contents before interacting.
}
```

Be careful with helpers that swallow exceptions. Exception suppression is appropriate only when failure is expected, bounded, and surfaced another way. It must not hide invariant violations or leave half-completed state.

`IsOccupied()` is broad convenience, not a universal safety oracle. Build a workflow-specific readiness predicate.

## Other useful areas

### ExcelServices

Provides enums and helpers around Lumina sheets, including jobs, worlds, territories, actions, items, equipment slots, and common game classifications. Verify row availability and language/data-sheet assumptions.

### MathHelpers

Includes vector, angle, bitmask, numeric, direction, and conversion helpers. State coordinate spaces and units explicitly.

### Logging and DuoLog

ECommons logging helpers integrate with Dalamud logs; `DuoLog` can surface selected messages to both logs and users. Keep routine diagnostics out of chat and never log credentials or unrelated sensitive state.

### EzDTR

Simplifies server-info-bar entry creation, text, tooltips, and clicks. Dispose entries and keep click handlers short.

### Circular buffers

Fixed-size buffers are useful for recent samples, rates, and diagnostic histories without unbounded growth. Choose capacity from a real retention need.

### AutoCutsceneSkipper and automation helpers

Automation helpers may change visible game behavior. Make them explicit opt-ins, restore state on disable/dispose, and keep their use within game/plugin rules and user intent.

## Choosing ECommons versus native code

| Question | Favor ECommons | Favor a local adapter |
| --- | --- | --- |
| Is there a maintained semantic helper for the exact addon/API? | Yes | No; only a loose low-level helper exists |
| Does the plugin already depend on ECommons? | Yes | No, and the need is one small stable Dalamud API |
| Is patch-sensitive logic centralized upstream? | Yes, and its update cadence is acceptable | The plugin needs tighter control or different evidence |
| Does the helper lifecycle fit the plugin? | Yes | The plugin has independent scopes or complex test seams |
| Can domain logic remain independent? | Yes, through a narrow adapter | Static helper access would spread through the core |
| Is the surface private reflection either way? | ECommons may simplify discovery | A strict local resolver may provide better validation and diagnostics |

The strongest pattern is often a narrow local interface implemented by an ECommons-backed adapter. Domain/workflow code depends on the interface, while the adapter contains current Dalamud, ECommons, pointer, IPC, or reflection details.

## Incremental adoption plan

### Low-risk first steps

1. Add and pin the reviewed dependency.
2. Initialize and dispose it in the plugin lifecycle.
3. Use `Svc` only in outer adapter code.
4. Use throttlers for new bounded polling.
5. Prefer `Callback.Fire` or an existing AddonMaster wrapper for new addon interactions.
6. Add regression tests around the adapter behavior.

### Medium-risk adoption

1. Replace duplicated low-level callback or chat conversion after behavior parity tests.
2. Introduce AddonMaster one addon at a time.
3. Use NeoTaskManager for a new workflow before migrating a mature runner.
4. Adopt EzIPC for a newly versioned public API.
5. Add event/hook helpers only with explicit dispose/reload tests.

### Deep integration only with measured benefit

- migrating a mature task runner;
- replacing a multi-window presentation architecture;
- converting established configuration and migrations;
- replacing all IPC wrappers at once; or
- enabling broad advanced modules.

Do not combine dependency adoption, architecture migration, and behavior changes into one unreviewable step.

## Review and validation checklist

- ECommons commit/package version and target framework are recorded.
- Requested advanced modules are the minimum necessary.
- `ECommonsMain.Init` and `Dispose` each run once.
- Static service access is confined to adapters where practical.
- Framework callbacks never block.
- Every queued task has a finite timeout and cancellation policy.
- Addon interactions validate the expected addon and prompt/state.
- Raw callback numbers are source-backed for the supported version.
- Throttle keys are stable and namespaced.
- Held keys, hooks, events, IPC, and DTR entries clean up on error/reload.
- Optional dependencies fail as feature-unavailable, not plugin-fatal.
- Reflection has version/type checks, read-back, diagnostics, and cache invalidation.
- Configuration migration and malformed-data recovery are tested.
- Source tests, build, package, and in-game runtime evidence are reported separately.

## Sources and freshness

- [ECommons repository](https://github.com/NightmareXIV/ECommons)
- [Observed ECommons snapshot](https://github.com/NightmareXIV/ECommons/tree/e6be8f0fd7786a9e1781db2e71cb5b9146f04980) - checked 2026-08-17
- [Observed project file](https://github.com/NightmareXIV/ECommons/blob/e6be8f0fd7786a9e1781db2e71cb5b9146f04980/ECommons/ECommons.csproj)
- [Observed NeoTaskManager source](https://github.com/NightmareXIV/ECommons/tree/e6be8f0fd7786a9e1781db2e71cb5b9146f04980/ECommons/Automation/NeoTaskManager)
- [Observed AddonMaster implementations](https://github.com/NightmareXIV/ECommons/tree/e6be8f0fd7786a9e1781db2e71cb5b9146f04980/ECommons/UIHelpers/AddonMasterImplementations)
- [Dalamud plugin development](https://dalamud.dev/plugin-development/)
- [Dalamud IPC API](https://dalamud.dev/api/Dalamud.Plugin.Ipc/)
- [Reflection and IPC guide](./reflection-and-ipc.html)

## Final rule

Use ECommons to centralize proven integration mechanics, then keep your plugin's policy and domain logic behind narrow, testable boundaries. Convenience is valuable; lifecycle clarity and current runtime evidence are mandatory.
