Monkey patches and load-order composition
This page summarizes compatibility risks. The canonical mechanics for wrapper ownership, descriptor handling, return values, and restoration are in Hook Composition Rules.
- Save originals before replacing methods and make installation idempotent with module flags, wrapper markers, or class markers.
- Preserve arguments and return values unless suppression is the explicit goal.
- Patch private or name-mangled methods only when necessary and isolate them behind small wrappers.
- Reversible mods restore originals in
fini(). - For synchronization hooks, snapshot old state, call the original first, then inspect new state.
- Use a thread-local guard if a hook can indirectly recurse.
- A single
_ORIGINALSdictionary is useful for large hook sets. - Several mods can patch the same target. A composable wrapper captures the method installed at patch time, calls it exactly once, and adds behavior before or after it.
- Capturing an original too early can bypass later wrappers. Restoring a stale original can remove another mod's hook.
- Before restoration, confirm the current method is still this mod's wrapper. Marker attributes help with ownership and duplicate detection.
- Preserve unknown events and values in broad hooks. Filtering a small known set is safer than replacing a complete client method with a decompiled copy.
- When an action exists in multiple views, patch every active entry point or a shared authoritative action beneath them. A server-changing action, for example, has separate lobby, standard battle-tab, and full-statistics battle-tab handlers.
- Collision-prone categories include avatar enter/leave lifecycle hooks, battle-feedback aggregation, roster or health-panel updates, battle statistics controllers, account/cache synchronization, localization resolvers, and shared text formatting helpers. Verify exact current targets before patching them.
Dependencies and optional services
- Prefer
dependency.descriptor(...)for a long-lived controller anddependency.instance(...)when resolving at the moment of use. @dependency.replace_none_kwargs(...)is convenient for helpers, but callers must still handle unavailable services.dependency.descriptor(...)resolves on attribute access. Before the dependency manager is configured it raisesAttributeError; a missing registered service also fails during access.dependency.instance(...)raisesDependencyErrorif the manager or service is unavailable.replace_none_kwargsresolves throughinstance(...)at call time and does not turn an unavailable service intoNone.- Treat seasonal and mode-specific modules as optional. A missing optional event or mode package should disable only the dependent integration.
- Scope entitlement and subscription checks to the behavior they actually own. If an entitlement provides one server-backed content domain, for example, its absence should not disable independent client-only presentation, local persistence, or view augmentation implemented by the same feature.
Event subscription identity
- Event systems remove the exact function object that was added.
- The client's
Event.Eventprevents duplicate registration of the same callable, iterates over a snapshot, logs listener exceptions, and re-raises them. One failing listener can therefore interrupt normal dispatch. Event.SafeEventlogs listener exceptions and continues dispatch. Do not assume an unfamiliar event type isolates faulty listeners; inspect its implementation.- In Python, save locally created handlers on the owning object before subscribing.
- In JavaScript, each
this.method.bind(this)call creates a new function. Store bound functions once and use the same references foronandoff. - The existing Gameface
MediaContextpattern that independently binds during subscribe and unsubscribe is a warning example; cached bound handlers are safer. - Make subscribe and unsubscribe operations idempotent to prevent duplicate notifications and retained dead views.
Event.EventsSubscriberis a reusable client helper for collecting ordinary and context-event subscriptions and removing them together. It is useful for components with many bindings, providedunsubscribeFromAllEvents()is called during teardown.
Performance-sensitive hooks
- Keep hooks on render, appearance-update, health-update, and event hot paths minimal.
- Build indexes and perform compression or networking outside hot paths.
- Cache exact lookups and define invalidation on purchases, item-cache sync, vehicle changes, view changes, and other source-state updates.
- Coalesce expensive view-model invalidations instead of refreshing for every packet.
- Visual assets that require disk reads, encoding, bridge transfer, or Flash decoding may be warmed before their first visible use, but warming is still workload on the game process. Enforce per-file and total-byte limits, cap frame counts, spread unavoidable main-thread bridge calls across bounded callbacks, cancel on context teardown, and reject stale asset/config selections.
- Prefer one validated sprite atlas over many loose animation frames when the renderer supports it; one bounded transfer and decode reduces bridge calls and cache churn. Validate atlas dimensions, frame count, image existence, and frame indexes before use.
- Do not resolve battle dependencies, create GUI components, read whole asset sets, or install permanent input hooks at module import merely to preload. Begin warming from an owned battle/view lifecycle point and retain a normal on-demand fallback if that early point is not ready.
Measuring Gameface workloads
With the same 400-cell surface, full per-frame DOM rebuilding cost more direct JavaScript work and native frame time than incremental text changes. Capping incremental updates at 10Hz kept frame times nearer the static surface. The table describes this comparison workload, not a universal update budget.
| Workload | Native mean frame time | JS mean direct work per frame |
|---|---|---|
| Static | 7.761–8.574 ms | 0.0026–0.0051 ms |
| Per-frame incremental | 9.670–13.312 ms | 0.4058–0.5695 ms |
| Per-frame rebuild | 14.118–15.576 ms | 1.5368–1.5623 ms |
| 10Hz coalesced | 8.563–9.212 ms | 0.0603–0.0640 ms |
These ranges are two observed phases, not confidence intervals or a universal node/frame budget. The replay moved and other mods remained present. Direct JS timings exclude asynchronous layout/paint/GPU work; native and frontend windows were related but not identical sample sets. Whole-process memory does not attribute per-mod or GPU allocations.
Prefer retaining owned nodes, coalescing changes and avoiding work when the compact state signature is unchanged. Select the update rate from the feature's latency needs, then compare equivalent workloads in both orders. See repeated cleanup checks and diagnostic provider provenance.