Freshness boundary. Examples were checked against ECommons commit e6be8f0fd7786a9e1781db2e71cb5b9146f04980 on August 17, 2026. Its project file reported base version 3.2.1.17 and target net10.0-windows7.0. Refresh repository source, package metadata, Dalamud API, and game structures before implementation.

01 / Overview

ECommons is a toolbox, not one feature.

It wraps recurring Dalamud and FFXIVClientStructs work: service access, frame-driven tasks, addon callbacks and semantic UI wrappers, throttling, configuration, events, hooks, logging, reflection, IPC, game data, and general utilities.

NeedUseful surface
Shared Dalamud service accessSvc
Ordered nonblocking workNeoTaskManager.TaskManager
Converted addon callback valuesAutomation.Callback
Semantic addon controlAddonMaster
Structured addon readsAtkReader
Time or frame pacingEzThrottler, FrameThrottler
Declarative cross-plugin contractsEzIPC
Last-resort private adapterDalamudReflector, ReflectionHelper

Adopt a helper when it provides a clearer maintained contract, not merely a shorter line. Compare lifecycle, errors, patch sensitivity, dependency cost, and fit with existing architecture.

02 / Architecture

Initialize once, dispose once, request only what you use.

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, ECommons accepts an IDalamudPlugin or IAsyncDalamudPlugin instance. Disposal cleans up ECommons-managed task managers, hooks, IPC, shared data, events, config/window helpers, throttlers, DTR entries, and related components.

Advanced modules

ModulePurposeCaution
AllAll advanced modulesHighest surface; avoid by default.
DalamudReflectorInternal Dalamud/plugin reflectionPrivate contracts and update monitoring.
ObjectFunctionsObject-related game functionsPatch-sensitive hooks.
ObjectLifeObject lifecycle trackingAdditional update/hook state.
SplatoonAPIOptional Splatoon integrationCross-plugin dependency behavior.
VfxTrackingVFX lifecycle trackingPatch-sensitive hooks.
ECommonsMain.Init(pluginInterface, this, Module.DalamudReflector);

Base initialization exposes many ordinary helpers. Passing Module.All as a default adds cost and private surfaces without evidence that a feature needs them.

03 / Source map

Know which layer owns the operation.

Automation

Task managers, callback conversion, chat, commands, key input, and cutscene helpers.

UIHelpers

Addon discovery, AddonMaster actions, and AtkReader snapshots.

Events and hooks

Login, territory, framework, addon, signature, and game-hook lifecycles.

IPC and shared data

Declarative calls/events plus cross-plugin shared storage.

Game and Excel

Player state, Lumina sheets, enums, lazy data, and game functions.

Presentation and utilities

Windows, ImGui, DTR, logging, throttling, math, strings, and buffers.

04 / Services

Svc centralizes injected Dalamud services.

using ECommons.DalamudServices;

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

This removes plumbing and creates a familiar adapter pattern. It can also hide dependencies and complicate tests when static access spreads. Keep domain logic behind narrow interfaces and use Svc at the Dalamud adapter boundary. Player availability never proves that territory, addon, conditions, or an optional dependency are ready.

05 / NeoTaskManager

Run stateful work on framework updates without blocking.

TaskManager is a frame-driven queue, not a background thread. The observed return contract is:

ResultMeaning
falseNot complete; poll again on a later update.
trueComplete; advance to the next task.
nullAbort the queue.
using ECommons.Automation.NeoTaskManager;

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

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

An action task issues a short operation once. A predicate observes state later. Never put synchronous I/O, Thread.Sleep, or a busy loop in a framework callback.

Operations, ordering, and events

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

The manager supports delays, front insertion, stacked sequences, and step-mode debugging. Document queue ordering so inserted work cannot violate invariants. Timeout, completion, exception, and companion-action callbacks can observe or alter task behavior; never extend time forever or swallow a failure into a wedged queue.

  • Stable task names appear in UI, logs, and tests.
  • Enter actions run once; predicates are safe to poll.
  • Timeouts are finite and operation-specific.
  • Retries are allowed only for idempotent work.
  • Dispose and cancellation clear active state.
  • Relog, teleport, and zoning use readiness observations, not delays alone.

06 / Automation helpers

Use semantic helpers, then verify game state.

Callback conversion

using ECommons.Automation;

// 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 converts common values such as int, uint, float, bool, string, and existing AtkValue entries. It solves memory conversion, not semantic discovery. Validate pointer, visibility, readiness, and callback meaning.

Chat and commands

Chat.SendMessage("A validated chat message");
Chat.ExecuteCommand("/example");
var safe = Chat.SanitiseText(untrustedText);

The validated path enforces byte and sanitization rules. SendMessageUnsafe should receive only already-validated internal bytes. Allow-list values when commands include external input, then observe completion.

Windows key input

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

Always release held keys on cancellation, error, focus loss, and disposal. Prefer game or addon APIs when available.

07 / UI helpers

AddonMaster communicates intent better than callback numbers.

The observed source contains well over one hundred semantic wrappers across dialogs, inventory, retainers, duties, shops, crafting, gathering, travel, social UI, portraits, and specialized content.

AreaExamples
DialogsSelectYesno, SelectString, SelectIconString, SelectOk, InputNumeric, Talk
Inventory and itemsInventory, InventoryLarge, ItemFinder, ItemSearchResult
RetainersRetainerList, RetainerSell, RetainerTaskAsk, transfer views
Duty and partyContentsFinderConfirm, settings/status, LookingForGroup
Craft and gatherGathering, RecipeNote, Synthesis
Shops and travelShop, currency exchanges, Teleport, world travel
if (GenericHelpers.TryGetAddonByName<AddonSelectYesno>(
    "SelectYesno", out var addon))
{
    new AddonMaster.SelectYesno(&addon->AtkUnitBase).Yes();
}

Confirm current generated types and constructors. A semantic Yes() still needs evidence that the expected prompt is open before a destructive confirmation.

AtkReader

AtkReader implementations expose structured snapshots for known UI surfaces such as select lists, retainers, synthesis, letters, banners, and specialized addons. Separate “read current state” from “perform action.” Never retain unmanaged pointers after the addon or callback scope can be reconstructed.

08 / State and pacing

Helpers contribute to readiness; they do not define it.

GameHelpers.Player reduces local-player boilerplate. GenericHelpers.IsOccupied() combines many conditions. Neither is a universal workflow oracle. Build a narrow readiness predicate for the next action and require consecutive passes after disruptive transitions.

Time and frame throttling

using ECommons.Throttlers;

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

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

EzThrottler.Reset("example-refresh");

Stable, namespaced keys prevent collisions. A throttle controls frequency; it does not provide cancellation, mutual exclusion, readiness, or idempotency.

09 / Configuration and windows

Choose helpers by lifecycle clarity.

EzConfig

using ECommons.Configuration;

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

Decide ownership, migrations, malformed-data recovery, save timing, multiple-file needs, and sensitive-data policy. Never silently discard user config after a parse or migration failure.

EzConfigGui

EzConfigGui can register a conventional main/config window, draw and open events, toggling, and config saving. It fits small conventional plugins. Multiple independently owned windows, complex docking, or testable presentation boundaries may justify an explicit Dalamud WindowSystem.

10 / Events and hooks

Central cleanup helps; current signatures remain mandatory.

ProperOnLogin and EzEvent

using ECommons.Events;

ProperOnLogin.Register(() =>
{
    // Recheck exact readiness and rebuild idempotently.
});

The event waits beyond the earliest login signal, but callbacks must remain idempotent. EzEvent helpers centralize framework, territory, logout, addon, and other lifecycle subscriptions. Queue longer work instead of running it in the handler.

EzHook

Hook helpers reduce signature and disposal boilerplate. They do not make a stale signature safe. Require a current signature or generated interop target, original-delegate path, exception containment, thread awareness, deterministic enable/disable, and live runtime verification.

11 / IPC and reflection

EzIPC simplifies declarations; the public contract still belongs to you.

Provider

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

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) { }
}

Document full tags, signatures, direction, thread context, absence behavior, and version semantics. Use applyPrefix: false only when a provider intentionally uses a different full-tag convention.

DalamudReflector and ReflectionHelper

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

Reflection remains a private contract. Prefer IPC. If reflection is necessary, pin the observed target, validate types, read back writes, invalidate on reload, and fail the optional feature cleanly. The companion Reflection and IPC guide provides the complete decision matrix, provider/subscriber code, direct discovery, Dropbox case study, caching, and troubleshooting.

12 / Additional areas

Use utility breadth without creating hidden policy.

ExcelServices

Typed Lumina helpers and enums for jobs, worlds, territories, actions, items, and classifications.

MathHelpers

Vectors, angles, bitmasks, directions, numeric helpers, and conversions. State units and coordinate spaces.

Logging and DuoLog

Dalamud logs plus selected user-visible messages. Never log credentials or unrelated sensitive state.

EzDTR

Server-info-bar entries, tooltips, and clicks. Dispose entries and keep callbacks short.

Circular buffers

Bounded recent samples and diagnostic histories. Choose capacity from a retention requirement.

Automation extras

Cutscene and other visible-behavior helpers need explicit opt-in and state restoration.

13 / Adoption

Introduce one adapter and one behavior at a time.

QuestionFavor ECommonsFavor local adapter
Maintained semantic helper exists?Yes, exact operationNo, only a loose low-level primitive
Dependency already present?YesNo, need is one small stable Dalamud API
Patch-sensitive logic centralized?Yes; update cadence fitsPlugin needs tighter evidence/control
Lifecycle fit?Matches plugin ownershipIndependent scopes or test seams differ
Private reflection either way?Convenient discoveryStricter typed result and diagnostics needed

Low-risk first

  1. Pin the reviewed dependency.Record source/package version and target framework.
  2. Initialize and dispose.Prove reload and teardown before more adoption.
  3. Confine services.Keep Svc in outer adapters where practical.
  4. Add pacing.Use throttlers for new bounded polling.
  5. Use a semantic UI helper.Adopt Callback or one AddonMaster wrapper for new work.
  6. Test the adapter.Keep domain logic independent of static/framework details.

Medium risk

Migrate callback/chat conversion after parity tests, introduce AddonMaster one addon at a time, try NeoTaskManager on a new workflow before replacing a mature runner, create a newly versioned EzIPC API, and test event/hook disposal before expanding.

Deep integration

Mature task runners, multi-window architecture, configuration migrations, all IPC wrappers, and broad advanced modules need measured benefit and staged migration. Do not mix dependency adoption, architecture replacement, and behavior changes in one review.

14 / Validation

Separate source confidence from runtime acceptance.

  • ECommons commit/package and target framework are recorded.
  • Advanced modules are the minimum necessary.
  • ECommonsMain.Init and Dispose run exactly once.
  • Framework callbacks never block.
  • Queued tasks have finite timeout and cancellation.
  • Addon interactions validate the expected addon and prompt/state.
  • Raw callback values are source-backed for the supported version.
  • Throttle keys are stable and namespaced.
  • Keys, hooks, events, IPC, and DTR entries clean up on error/reload.
  • Optional dependencies become feature-unavailable, not plugin-fatal.
  • Reflection has version/type checks, read-back, diagnostics, and invalidation.
  • Configuration migration and malformed-data recovery are tested.
  • Static tests, build, package, and in-game evidence are reported separately.

15 / Sources

Read the implementation before freezing an example.

Final rule. Use ECommons to centralize proven integration mechanics, then keep policy and domain logic behind narrow, testable boundaries. Convenience is useful; lifecycle clarity and current runtime evidence are mandatory.