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:
| Getter | Inherited cleanup contract |
|---|---|
_getListeners | Two or three fields identify event, handler and optional scope. A fourth priority field is accepted on registration and sliced off before removal. |
_getRestrictions | Use 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.