Defer expensive view refreshes

A ViewComponent constructor and its high-level navigation callbacks run on the lobby/UI thread. Building a large payload there can delay the native route transition, and a current-object event may also fire while the route is being torn down.

  • Construct the component with a small loading model, then schedule the full refresh through one owned BigWorld.callback.
  • Cancel the previous callback before scheduling another so bursts of model, vehicle, or cache events coalesce into one refresh.
  • Clear the stored callback ID before executing the work and cancel any pending callback in _finalize.
  • Cache or group repeated calculations. Deferral changes when synchronous work runs; it does not move that work off the game thread.

Choose current-state refresh or retained callbacks

Current public source; version-sensitive. The client's OverlapCtrlMixin gates widget updates on route, window and overlay policy. Its deferred-call list retains callable and argument references. It does not snapshot payloads, deduplicate calls or start a timer merely because work was added.

This short banner-presenter excerpt shows the ownership choice:

def _onBannerUpdate(self, banner):
    if self.hasDeferModelUpdate:
        self.deferUpdate(self._updateBanner, banner)
    else:
        self._updateBanner(banner)

Later execution uses the retained banner object; it is not the banner's earlier state frozen at notification time. A separate full-refresh flag requests reconstruction from current data. In the gate's drain method, a full refresh takes precedence over deferred calls and clears them after normal completion. Direct queueUpdate() with an open gate instead calls the raw refresh without that drain's list-clearing step. Check the actual path before treating a rebuild as callback replay or cancellation.

isUpdateQueued exposes only the deferred-call flag. A false value does not rule out a pending full refresh. The Battle Pass presenter uses that flag to avoid another pending callback, then explicitly attempts a drain; it also adds initialized-space readiness to the base gate. Those are caller-specific policies. Recheck the owning presenter and its release events, and choose explicitly between an owned data snapshot and recomputation from current state. Finalization clears retained work and rejects new queue requests. These source contracts do not establish lossless notification history or rendered adoption; no live widget or native rendering was exercised.

Cleanup and ownership

Every persistent object needs one owner and one teardown path.

Automatic Python subscriptions need stable owners

ViewImpl subscribes during loading and unsubscribes during finalization. Its GUI helper reevaluates _getEvents, _getListeners and _getRestrictions when releasing them; it does not replay a saved registration ledger. Preserve the original event owners and handler identities through superclass finalization. Resolving replacement controllers or clearing fields needed by those getters can leave earlier subscriptions attached.

Check each subscription group's argument and removal contract separately:

GetterInherited cleanup contract
_getListenersTwo or three fields identify event, handler and optional scope. A fourth priority field is accepted on registration and sliced off before removal.
_getRestrictionsUse two or three fields. The helper forwards the whole tuple to removal; a fourth priority field can register successfully but raises TypeError before removal, skipping later cleanup groups.

For _getCallbacks, automatic removal finds only methods bound to that same view. A lambda, partial or callable object capturing the view does not acquire that removal identity; retain its exact path/handler pair and release it explicitly. See ClientUpdateManager removal and EventBus registration/removal signatures alongside the GUI helper below. Inspected restriction getters use three fields; this wrapper constraint is not evidence of a production leak.

Registration and release are incremental. A later failure does not roll back earlier registrations, and a release failure can skip the remaining groups. A raising _finalize can also bypass the later Python event-manager, sound-manager and retained-model cleanup in View._cFini. Give partially acquired mod resources an explicit cleanup path and preserve traceback diagnostics for unexpected failures.

These are version-sensitive Python control-flow contracts, verified against the GUI subscription helper, ViewImpl and the Wulf View wrapper at latest public source. They do not establish native destruction behavior, recovery after an exception, or a failure in a particular client view.

Owned object Cleanup
Wulf window and Gameface view Destroy at the owning lobby/battle page's exit and clear the reference.
Resource readiness callback Invalidate with a generation token; unsubscribe when the manager supports removal.
Model callback or data listener Remove using the stored callback ID and exact model identity.
Mutation observer Disconnect and remove any DOM nodes owned by the feature.
Scale listener Unregister before the view is destroyed.
Input or drag loop Cancel callback IDs, unbind handlers, and clear held state.
Page method wrapper Restore only when the installed method is still this wrapper.

Submodel presenters separate deactivation from model release

The Python SubModelPresenter base subscribes before setting its loaded flag and clears the flag before unsubscribing. Its clear() only drops the retained model proxy; it does not finalize, unsubscribe, reset that flag or release the parent. Inspect subclass overrides and their superclass calls separately. A base loaded flag is not a full subclass data-readiness receipt.

The model starts as a weak proxy, while the parent is stored as supplied. An actual parent object and a weak parent proxy therefore have different retention rules. A cached presenter owner finalizes the active presenter on a normal page switch, then initializes its cached target without clearing the model. On final teardown it finalizes the current presenter, clears cached presenters and releases their map. It supplies weak parent proxies. Preserve the owning view's deactivation, reuse and final-release sequence.

These version-sensitive Python contracts were checked with synthetic weak-referenceable objects and recording subscription methods. Actual Wulf proxies, native views, client subscriptions, installed bytecode and live teardown were not exercised; the checks do not establish universal idempotence or native model lifetime.