Each example focuses on one mechanism; read its ownership and lifecycle limits before adapting it. Confirm imports and client symbols for the game build you target.
How to use these recipes
Copy the ownership pattern
Keep callback IDs, subscriptions, workers, and original callables in mod-owned state.
Replace only verified symbols
Imports and client method names are build-dependent. Confirm them in current source and at runtime.
Test every transition
Exercise repeated initialization, account changes, battle entry, battle exit, and shutdown.
1. Cancellable delayed action
This illustration retains one callback handle and admits the delayed action when its captured generation still equals the current value. Connect real context transitions to invalidation and check feature readiness separately before adapting it; the snippet supplies no lifecycle wiring.
Verification scope: authored Python control flow, compared with current
CallbackDelayer and CallbacksSetByID
ownership paths. Their keyed scheduling and repetition contracts differ from this recipe.
This comparison does not establish native callback timing, cancellation acknowledgement or
thread safety; see runtime and callback guidance.
import BigWorld
_callback_id = None
_generation = 0
def schedule(delay, action):
global _callback_id
cancel()
token = _generation
def run():
global _callback_id
_callback_id = None
if token != _generation:
return
action()
_callback_id = BigWorld.callback(delay, run)
def cancel():
global _callback_id
if _callback_id is not None:
BigWorld.cancelCallback(_callback_id)
_callback_id = None
def invalidate_context():
global _generation
_generation += 1
cancel()
schedule cancels the retained handle first, then captures the generation and stores
the result of BigWorld.callback. It passes the delay through unchanged and returns
no handle or completion result. With no retained ID, cancel does nothing. If native
cancellation raises for a retained ID, that ID remains and scheduling stops before replacement.
If scheduling raises after cancellation returned, the old ID is already cleared and no new
result is stored. These are Python ordering facts, not a native failure guarantee.
run clears the shared handle before checking the token. An unequal generation
returns; equality calls the retained action() with no arguments. It reads no
account, arena, vehicle, view or active/readiness state. The callable and the state it reads
are not copied into a snapshot. Its return value is discarded; an exception escapes after the
handle was cleared. Clearing first allows an action to schedule a successor without a later
cleanup assignment overwriting that handle. There is no automatic repetition, retry, rollback
or successful-action receipt, and no further guard once the action has entered.
cancel and replacement scheduling do not advance the generation or permanently
disable the owner. A normal cancellation return means the optional native call returned and
the handle was cleared; it does not independently prove that an action never ran or cannot
still enter. If an older cancelled or replaced closure were invoked in the same
generation, this check would admit it. This is a conditional code path, not an observed native
cancellation result or a latest-request-wins promise.
invalidate_context increments the generation before attempting cancellation.
That increment survives a cancellation exception, while the old ID remains stored. An unequal
older token would then be rejected if invoked; new scheduling is still permitted and captures
the advanced generation. Invalidation registers no events and restores no feature resources.
Every run, including one rejected for an old generation, clears the current
shared slot without comparing its own callback ID. If an old closure were
delivered after a newer ID was stored, it would erase tracking of that ID before the token
check. One stored slot does not prove at most one pending native callback, request identity,
delivery order or synchronized ownership. Verify the supported delivery and cancellation
boundary in the feature that owns the action.
The literal _generation name is also used by
recipe 5; pasting both into one module shares
a counter rather than creating independent channels. The
loader shell describes the two increments when both
invalidators finish. These fragments contain no mod version or exception reporting. A shipped
adaptation needs version attribution and
traceback-bearing diagnostics
for caught unexpected failures. This review executed no recipe, scheduler stub, callback,
cancellation or game operation; existing historical observations keep their original scope.
2. Composable method wrapper
This illustrative pattern owns one ordinary instance-method patch. Each installation captures its predecessor in a closure. Teardown disables its added behavior and restores the slot only while it still owns it; a later wrapper can safely keep calling the disabled wrapper.
Verification scope: Python wrapper composition, not a verified client hook. Choose the target from current callers and confirm its signature and lifetime. See retained callables and source verification before adapting it.
import logging
_LOGGER = logging.getLogger(__name__)
_patch = None
def install(target_class, method_name):
global _patch
if _patch is not None:
return
original = getattr(target_class, method_name)
state = {'active': True}
def wrapped(self, *args, **kwargs):
result = original(self, *args, **kwargs)
if state['active']:
try:
on_after_call(self, result)
except Exception:
_LOGGER.exception('Post-hook behavior failed')
return result
setattr(target_class, method_name, wrapped)
_patch = (target_class, method_name, original, wrapped, state)
def uninstall():
global _patch
if _patch is None:
return
target_class, method_name, original, wrapped, state = _patch
state['active'] = False
if vars(target_class).get(method_name) is wrapped:
setattr(target_class, method_name, original)
_patch = None
def on_after_call(instance, result):
pass
_patch is one module-wide slot: while it is non-None,
install returns without examining even a different requested target. Otherwise it
captures the current attribute, creates a fresh active-state dictionary and wrapper, assigns
the wrapper, and only then stores its tuple receipt. Lookup failure stores nothing. Assignment
failure skips that receipt; a custom setter may already have had side effects. There is no
rollback, concurrent-install protection or per-target registry.
Each wrapper calls its captured predecessor once along that invocation path, passing
self, positional arguments and keyword arguments through without copying their
referenced objects. A predecessor failure escapes before the post hook. After a normal return,
the active flag is checked, so teardown during the predecessor can suppress that post hook.
The current global on_after_call(self, result) receives only the instance and
result; it does not receive the original argument list. Its return is discarded and its
pass body implements no feature.
The wrapper catches Exception only around the admitted post hook and records
its active traceback. It returns the predecessor's result object if that path and logging
return normally. A predecessor error, an exception outside Exception, or a logging
failure escapes. There is no recursion, readiness or context guard and no cancellation of work
an adapted post hook may start.
uninstall marks the saved wrapper inactive before examining the live class
dictionary. It restores the predecessor only when that raw dictionary value is the saved
wrapper object. A failed lookup or restoration leaves the tuple present and inactive,
suppressing a new install. After a normal path, the tuple is cleared even when an interposed
wrapper prevented restoration. This does not wait for in-flight calls or recall a post hook
that already passed its active check.
Call uninstall() from the owner’s teardown. Reinstallation creates a fresh active
wrapper; any retained old wrapper remains pass-through. This cannot remove an old wrapper from
another mod’s chain, so avoid repeated patch cycling when a process-lifetime hook is sufficient.
The ownership check reads the class dictionary because Python 2 class access creates an
unbound-method object. This raw-identity check does not unwrap an unbound method another
owner restored into the dictionary, inspect inherited slots or handle arbitrary descriptors.
Clearing the tuple does not release predecessors still retained by old closures. Static
methods, class methods and other descriptors need their own binding analysis. Before shipping
an adaptation, provide version attribution and
traceback-bearing diagnostics
for caught unexpected failures. The post-hook catch does not diagnose installation or teardown
failures on its own. No authored wrapper, hook target or client scenario was run for this review.
3. Explicit event ownership
This illustration guards calls with one module-wide flag. It does not retain the actual
event or subscribed delegate. Repeated startup and teardown are locally guarded only while
callers preserve that pair and the event operations return as assumed. The unspecified
events.onChanged is not constructed or assigned an event type here.
_subscribed = False
def start(events):
global _subscribed
if _subscribed:
return
events.onChanged += _on_changed
_subscribed = True
def stop(events):
global _subscribed
if not _subscribed:
return
events.onChanged -= _on_changed
_subscribed = False
def _on_changed(*args, **kwargs):
handle_change(args, kwargs)
def handle_change(args, kwargs):
pass
start sets the flag only after events.onChanged += _on_changed
returns, including assignment of the augmented result back to the attribute. A true flag
suppresses startup even for another container. stop reads the supplied container's
current event and the current global handler, subtracts it, then clears the flag. Neither
function saves the original pair. Replacing the event attribute or rebinding the handler
between calls can therefore leave the original listener attached.
For current ordinary Event.Event, addition
checks callability and normal membership/equality; a normal return need not add a listener.
Removing an absent listener returns normally. Thus start(A) followed by
stop(B) on distinct ordinary events can reset the flag while leaving A subscribed.
External clearing can cause the opposite drift: a true flag with no listener. The flag
does not verify membership or acknowledge release of the original registration.
An add or attribute-write failure leaves the previous false flag; a removal or attribute-write failure leaves the previous true flag. Side effects may already have occurred before the failure, and there is no rollback or membership reconciliation. Retain or otherwise guarantee the actual event and compatible delegate through teardown; follow the event ownership guidance for the event type and lifecycle in use.
_on_changed calls handle_change(args, kwargs) with two positional
arguments: the positional-argument tuple and keyword-argument dictionary. It does not expand
them again or deep-copy referenced payload objects. The return value is discarded and
pass implements no feature. Replacing that placeholder with a raising handler lets
the error escape. Ordinary Event logs and re-raises listener failures, stopping
later listeners in that dispatch; SafeEvent logs and continues.
Ordinary Event dispatches a snapshot, so a listener removed during dispatch may
still be called from that snapshot. This handler has no active, context or readiness guard.
Unsubscribing does not cancel timers, workers or requests a real handler may start. No
per-owner registry, synchronization or complete lifecycle cleanup is provided. Before shipping
an adaptation, add version attribution and preserve
traceback-bearing diagnostics for caught unexpected failures.
These are source-reviewed conditional paths; no authored handler or event scenario was run.
4. Backup-assisted JSON settings
This Illustrative example reads defaults, a primary file or a backup, then stages writes before rotating the active file. It demonstrates a recovery policy; schema validation, diagnostics and serialized ownership must be supplied by the integrating mod.
import json
import os
DEFAULTS = {'enabled': True, 'schemaVersion': 1}
def load_settings(path):
data = dict(DEFAULTS)
try:
with open(path, 'rb') as stream:
loaded = json.load(stream)
except Exception:
backup = path + '.bak'
if not os.path.exists(backup):
return data
with open(backup, 'rb') as stream:
loaded = json.load(stream)
if isinstance(loaded, dict):
data.update(loaded)
return data
def _json_file_is_valid(path):
with open(path, 'rb') as stream:
try:
json.load(stream)
except ValueError:
return False
return True
def save_settings(path, data):
directory = os.path.dirname(path)
if directory and not os.path.isdir(directory):
os.makedirs(directory)
temporary = path + '.tmp'
backup = path + '.bak'
try:
with open(temporary, 'wb') as stream:
payload = json.dumps(data, indent=2, sort_keys=True)
stream.write(payload.encode('utf-8'))
stream.flush()
os.fsync(stream.fileno())
if os.path.exists(path) and _json_file_is_valid(path):
if os.path.exists(backup):
os.remove(backup)
os.rename(path, backup)
elif os.path.exists(path):
os.remove(path)
try:
os.rename(temporary, path)
except Exception:
if not os.path.exists(path) and os.path.exists(backup):
os.rename(backup, path)
raise
finally:
if os.path.exists(temporary):
os.remove(temporary)
Read selection and schema: load_settings shallowly copies the
current DEFAULTS before its handler. The shown defaults are immutable scalars;
future nested defaults would remain shared. Any Exception from primary
opening, reading/parsing or closing silently selects the backup branch. This covers more
than missing files or malformed JSON and emits no diagnostic reason. Failures during the
defaults copy, backup-name construction, backup probing/opening/parsing/closing or later
merge propagate; exceptions outside Exception are not caught.
A false backup-existence probe returns defaults; that probe is not proof of absence when
filesystem access fails. A readable backup is parsed once, without another fallback.
A parse-successful primary wins even if it is an array, scalar, null or unsupported schema:
non-dictionaries leave defaults unchanged without trying the backup; dictionaries shallowly
overlay every key, including unknown keys and schemaVersion, with nested loaded
values inserted by reference. There is no deep copy/merge, type/range/schema validation or
migration. Loading never repairs the active path, saves defaults or recovers a temporary
file. The returned dictionary does not identify its source or certify persistence.
Parse admission: _json_file_is_valid catches only a
ValueError raised during json.load. That type alone does not prove
malformed bytes. Opening, other read/parse failures and context-manager exit failures
propagate; closing can override a pending false return. Any decoded JSON value passes,
not just a supported settings dictionary. The normal
Python 2.7 JSON defaults
also admit non-finite numbers. The decoded value is discarded. Saving uses this check on
the old active file, with no validation/readback of the new payload.
Staging: Parent-path setup, directory creation and temporary/backup name
construction precede the outer cleanup block; setup failures propagate without that cleanup.
The fixed temporary file is opened/truncated before json.dumps serializes
the supplied object. Serialization uses normal defaults with sorted keys and indentation,
then forms UTF-8 bytes in memory. It does not validate settings or snapshot mutable input.
Writing, flushing, fsync and closing all precede old-file rotation; any of those stages can
fail after truncation. The full read/string/bytes allocations have no size, memory or
latency bound, and there is no new-payload comparison.
Rotation: If the old active file parses, an existing backup is removed before moving that active file to the backup name, even if its schema is unusable. If parsing returns false and a second active-existence probe succeeds, the active file is removed while any older backup is retained. A raised validation error does not enter this removal branch. Probes and mutations are separate operations. Backup deletion, active removal and the active-to-backup rename occur outside the final-promotion handler; their failures propagate through cleanup without that handler's restore attempt, and earlier side effects have no general rollback.
Restore and cleanup: Only a final-promotion Exception attempts
restore, and only when the active probe is false and the backup probe true. The backup is
not parsed or tied to this write; it may be older data retained after invalid-active removal.
Successful restore consumes the backup name and still re-raises the promotion failure.
Failed probing/restoration can replace that failure; an unmet restore condition re-raises
without repair. The unguarded finally then removes any remaining temporary file,
including after failed serialization, rotation or restore. A cleanup error can replace a
pending exception or surface after promotion; remaining staged data may be deleted.
Normal completion returns None, with no separate status, retry, memory rollback
or post-promotion verification.
Ownership and diagnostics: One serialized writer must own the active, backup and fixed temporary names for the full read-modify-write policy. Directory creation, existence probes and unique temporary names alone would not coordinate that shared pair; this example has no lock, generation guard or concurrent-reader/external-edit coordination. The active name is absent between the two renames. Temporary-file flush/fsync does not synchronize directory changes or make the sequence one atomic replacement; exception-driven restore does not handle process crashes or establish power-loss durability. See the storage and replacement boundaries.
The silent primary fallback is a diagnostic limitation. A shipped mod must distinguish expected recovery from unexpected failures, preserve traceback-bearing records inside its exception handlers and provide its module version for optional error attribution. This fragment supplies no logger/version or reporter integration; its writer/helper propagate failures for an integrating caller to report. The standard Python 2.7 exception rules explain the cleanup precedence, not the embedded client's native file behavior. This review executed no authored helper, thread, filesystem-failure fixture or game client, and establishes no disk durability or real filesystem-failure result.
5. Bounded background request
This Illustrative helper limits admission of one returned body and checks a generation before calling the result handler. It is not a complete request owner: worker admission, lifecycle invalidation, cancellation and delivery receipts remain separate.
import logging
from contextlib import closing
import BigWorld
import threading
import urllib2
_LOGGER = logging.getLogger(__name__)
_generation = 0
_MAX_RESPONSE_BYTES = 262144
def request(url, on_result):
token = _generation
def worker():
result = None
error = None
try:
with closing(urllib2.urlopen(url, timeout=5.0)) as response:
body = response.read(_MAX_RESPONSE_BYTES + 1)
if len(body) > _MAX_RESPONSE_BYTES:
raise ValueError('Response exceeds size limit')
result = body
except Exception as exc:
_LOGGER.exception('Background request failed.')
error = str(exc)
def deliver():
if token == _generation:
on_result(result, error)
BigWorld.callback(0.0, deliver)
thread = threading.Thread(target=worker)
thread.daemon = True
thread.start()
def invalidate_requests():
global _generation
_generation += 1
Record unexpected failures inside the original exception handler before forwarding an error value or raising later. A later error message alone cannot reconstruct the original failing stack. Keep completion signals, context checks and delivery policy unchanged; see traceback-bearing logs. The diagnostic branches were checked with inert scheduling and in-memory logging substitutes, without real threads, waits, network requests or game-thread execution.
The worker requests a prefix of _MAX_RESPONSE_BYTES + 1 and rejects a
returned body longer than 262144 bytes. Reading only the limit could accept a truncated
prefix silently. An empty or short body is still accepted as raw data; this check does not
validate framing, completeness, schema, decompressed size or total transport/process memory.
The acquired response is owned by contextlib.closing:
entry returns that object and exit attempts its close without swallowing exceptions.
Success is assigned after block exit, so open, read, excess-byte or close failures leave
result=None. A close attempt is not proof of successful cleanup; a close failure
during another error does not promise to preserve both failures.
Static inspection separately confirmed closing's close-on-exit behavior in CPython 2.7.18; the linked current reference does not verify the embedded runtime. Selected worker checks used fake openers, returned responses, thread launchers and scheduling with recorded read/close operations. They cover the size boundary and failure delivery, without real requests, sockets, threads or waits. The size policy does not validate HTTP framing, schema or transport completeness, and no live resource leak was diagnosed.
Every request starts a separate daemon and implicitly returns None.
There is no concurrency cap, busy flag, worker/request handle or join/stop path; construction
and start failures propagate to the caller. URL and handler references are retained without
validation or copying, and no URL scheme or endpoint policy is supplied. Concurrent requests
have no aggregate memory bound.
invalidate_requests only increments the generation and is not connected to an
account, arena, view or destruction event here. Older deliveries silently skip the handler:
invalidation does not stop workers, close responses, retract callbacks or report cancellation.
All calls within the same generation can deliver, with no latest-request-wins or ordering
guarantee. The check runs only at the start of delivery; the owner must define current identity
and the lifecycle that invalidates it.
The selected current urllib2.urlopen
forwards timeout=5.0 to the chosen opener. That excerpt establishes argument
forwarding, not an end-to-end deadline for launching, reading, closing, scheduling or handling.
This helper measures no total elapsed time and has no retry budget.
The original exception handler records a traceback before forwarding text. The error text
has no size cap or redaction, and logging or text-conversion failure has no separate delivery
path. BigWorld.callback runs outside that handler and its ID is discarded, so
scheduling failures are not caught there. The authored call site does not establish native
thread safety or guarantee eventual delivery.
On generation equality, delivery passes the original result/error references to the retained handler. Handler exceptions propagate; there is no retry, serialization or receipt of successful application. A returning worker does not acknowledge current context or handler execution.
6. Context-owned feature controller
This Illustrative controller shows context-key and flag ordering. Its resource hooks are placeholders; it does not implement a complete account or battle lifecycle.
class FeatureController(object):
def __init__(self):
self._active = False
self._context_key = None
def enter(self, context_key):
if self._active and self._context_key == context_key:
return
self.leave()
self._active = True
self._context_key = context_key
self._install_for_context()
def leave(self):
if not self._active:
return
self._remove_for_context()
self._context_key = None
self._active = False
def _install_for_context(self):
pass
def _remove_for_context(self):
pass
The hooks are empty: this class installs no subscriptions, callbacks, views or resources.
Construction stores only an inactive flag and empty key. enter skips an active
equal key without checking readiness or whether prior setup succeeded. Otherwise it leaves
the old context, records the new active flag and retains the supplied key before installation;
the key is neither copied nor validated against a current client owner.
If installation raises, the new active/key state remains and another equal-key
enter returns early. A failure leaving the old context prevents installing the
new one. There is no rollback or partial-resource record here.
leave calls removal before clearing the state. A removal failure retains the
fields, so a later call can retry removal without proof that repeating it is safe. With the
literal pass hooks, normal calls only change flags. Overrides must define owned
acquisition, cleanup, idempotence and failure diagnostics.
These flags are not completion receipts. There is no separate in-progress state, guaranteed cleanup after an error or mod-owned exception logger. Set an installed flag after required setup succeeds, or define an explicit partial-setup and recovery policy; see lifecycle ownership.
7. Minimal mod lifecycle
Pseudocode for a single-file mod shell with subscription placeholders and explicit failure limits.
_started = False
_controller = FeatureController()
def init():
global _started
if _started:
return
_started = True
install_loader_subscriptions()
def fini():
global _started
if not _started:
return
remove_loader_subscriptions()
_controller.leave()
invalidate_requests()
invalidate_context()
_started = False
FeatureController comes from the preceding illustration. The subscription
helpers are undefined placeholders; no event wiring, transition handler or current-context
seed is provided. The controller is created at import, retained after fini and
not entered by init.
init records _started=True before subscription setup. A missing or
failing helper leaves it true, so the next call skips installation. This ordering supplies
no rollback or proof of complete setup.
fini removes subscriptions, leaves the controller, invalidates requests,
invalidates delayed context, then clears the flag. A failure stops subsequent steps and the
final reset; a retry can repeat earlier successful operations. There is no per-step catch
or finally. Invalidation follows cleanup rather than preceding it, so the
shell does not guarantee that stale work was rejected before cleanup began.
Request invalidation only advances its generation; it does not stop workers or acknowledge
cancellation. Delayed-context invalidation advances its generation and attempts cancellation
of any retained callback ID. The literal request and delayed-action fragments both name
the module global _generation: pasting them into one file shares that state,
and both successful invalidators advance it twice. Define the intended state ownership
when adapting them.
The selected current gui.mods loader
calls module hooks through _callModMethod. In that unwrapped built-in function,
AttributeError is caught around lookup and execution, so such an error inside a
hook can disappear while recipe cleanup remains incomplete. Other exception types are not
caught by that boundary. The fragments supply no version attribution or mod-owned exception
diagnostics; use traceback-bearing diagnostics
for caught unexpected failures when implementing a mod.
This is authored control-flow and selected Python loader inspection. No hook, subscription helper, recipe, cancellation, loader broadcast or game API was executed; it is not runtime evidence of complete setup or cleanup.
Verification checklist
- Every imported private symbol exists in the target client build.
- Initialization and teardown can run more than once without duplication or exceptions.
- No worker thread reads or mutates engine-owned objects.
- Delayed and asynchronous results carry a context or generation check.
- The feature works after entering and leaving battle repeatedly.
- Errors identify the failed subsystem without flooding the log.