1

Rewrite Asynchronous Loader to Prevent Speeder Crash with Smart Pointers and Cancellation Tokens

Suggested by
on August 11, '26
Under Review
Proposal: SWG Client Asynchronous Loader Architectural Rewrite

Objective: Resolve the legacy 0xc0000005 "Speeder Crash" caused by race conditions during rapid asynchronous object loading and deletion.

1. Executive Summary

The SWG client engine (2003) relies on a main thread to manage the game world and an AsynchronousLoader thread to fetch and construct heavy graphical assets (meshes, shaders, textures) in the background.

When a player moves rapidly across the terrain (e.g., riding a speeder), they trigger a massive queue of asynchronous load requests. Simultaneously, the main thread's AlterScheduler culls objects that fall out of range.

The Bug: The engine currently uses raw pointers (void* data) for its loader callbacks and direct memory deallocation (delete object;) in the AlterScheduler. If the AlterScheduler deletes an object while the background thread is still processing its graphical assets, the background thread eventually attempts to write the loaded assets to the now-freed memory address, resulting in a fatal Access Violation (0xc0000005).

2. Current Architecture Flaws
  • Raw Pointer Callbacks: The AsynchronousLoader::add(Callback, void *data) method passes raw pointers. The loader has no way of knowing if the data pointer is still valid when the callback fires.
  • Aggressive Deallocation: AlterScheduler::delete forcefully frees memory immediately. It does not check if the object has pending asynchronous jobs.
  • Non-Thread-Safe Cancellation: While AsynchronousLoader::remove exists, it struggles to safely cancel a job that is already actively processing on the background thread.
3. The Proposed Architectural Rewrite

To solve this, the object lifecycle management and the asynchronous loader pipeline must be modernized to guarantee thread safety. This involves three major phases:

Phase 1: Reference Counting & Deferred Deletion (Smart Pointers)

The core of the fix requires abandoning aggressive raw delete calls in favor of a reference-counted lifetime model (similar to std::shared_ptr or the engine's existing intrusive Pointer<> system, if applicable).

  1. Loader Ownership: When the main thread requests an asset, it passes a strong reference (or smart pointer) to the AsynchronousLoader.
  2. Deferred Deletion: AlterScheduler::delete must be refactored. Instead of calling delete object;, it will mark the object as isDead = true and release its main-thread reference.
  3. Safe Memory Free: The memory will only be deallocated once the AsynchronousLoader finishes its job, realizes the object is dead, and releases the final reference.
Phase 2: Asynchronous Job Cancellation Tokens

To prevent the client from wasting CPU and I/O cycles building assets for an object that is already dead, we need a cancellation mechanism.

  1. Job Tickets: AsynchronousLoader::add should return a JobTicket or CancellationToken.
  2. Pre-flight Checks: The background thread must check the CancellationToken immediately before reading the disk, before processing the mesh, and before dispatching the callback.
  3. AlterScheduler Hook: When AlterScheduler kills an object, it triggers the cancellation token. The background thread safely aborts the job.
Phase 3: Main-Thread Callback Synchronization

The AsynchronousLoader::processCallbacks() function currently executes the raw void* data callbacks on the main thread once the background thread completes.

  1. Validation Wrapper: Wrap all callbacks in a functor that validates the smart pointer.
  2. If the object was marked isDead by the AlterScheduler while the job was in flight, the callback immediately discards the loaded asset and gracefully exits instead of attempting to apply a shader to a nullified object.
4. Implementation Scope & Files Affected

This rewrite will touch foundational systems of the SWG client.

  • src/engine/shared/library/sharedFile/src/shared/AsynchronousLoader.h/cpp
    • Refactoring to support smart pointers/cancellation tokens instead of void*.
  • src/engine/shared/library/sharedObject/src/shared/object/AlterScheduler.cpp
    • Refactoring lines 1284-1294 to use deferred deletion/ref-counting instead of delete object;.
  • src/engine/client/library/clientObject/src/shared/object/ClientObject.cpp
    • Updating how graphical assets are bound to the object post-load.
5. Risks & Considerations
  • Memory Leaks (Reference Cycles): Implementing reference counting in a complex object hierarchy can lead to circular references (e.g., an object holds a reference to a child component, and the component holds a reference back). weak_ptr concepts must be strictly enforced.
  • Stuttering / CPU Overhead: Introducing atomic reference counting and thread-safe locks on the asynchronous queue may slightly increase CPU overhead, requiring profiling to ensure client frame rates aren't degraded.
No comments yet.
Sign in to comment on this suggestion. Sign In