Skip to content

Introduce serial and parallel caster modes with SoA simulation architecture#39

Draft
weenachuangkud wants to merge 129 commits into
mainfrom
major
Draft

Introduce serial and parallel caster modes with SoA simulation architecture#39
weenachuangkud wants to merge 129 commits into
mainfrom
major

Conversation

@weenachuangkud
Copy link
Copy Markdown
Owner

@weenachuangkud weenachuangkud commented May 8, 2026

Summary by CodeRabbit

  • New Features

    • Added serial and parallel caster modes, Motor6D transform movement with pooling, and client-side serial/parallel FPS benchmarks.
  • Bug Fixes

    • Fixed high-fidelity sub-ray direction timestep calculation.
  • Documentation

    • Added full API reference, updated README (Rojo install + simplified examples), new architecture and comparison guides, and expanded changelog.
  • Chores

    • Updated TODOs and performed performance-focused refactor to SoA simulation and related optimizations.

- ActiveCastSerial: simplified, uses SerialSimulation
- BaseCastSerial: uses SerialSimulation for all casts
- SerialSimulation: single RunService, SoA, queue technique like SwiftCast
- Add ParallelSimulation: SoA pattern for each Actor
- Update BaseCast: uses ParallelSimulation instead of per-cast Heartbeat
- Each Actor now has one RunService handling multiple casts
- ActiveCast: Remove UpdateConnection (old per-cast heartbeat)
- ActiveCastSerial: Remove metatable, use pure data structure
- Now internal uses SoA, external exposes OOP API
- Add Motor6DPool for efficient Motor6D pooling
- Add MovementMethod to FastCastBehavior (BulkMoveTo/Transform)
- Update SerialSimulation to support Transform mode
- Per-caster configuration, not global
- Update ParallelSimulation with Motor6D Transform support
- Same as SerialSimulation implementation
- Added 0.1.0 changelog entry documenting new features
- Created Serial benchmark (benchSerial.client.luau)
- Created Parallel benchmark (benchParallel.client.luau)
- Updated TODO.md with all completed items
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e7c66a3b-c7c2-4c2b-9eb2-1f6c49c0f5b9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

FastCast2 is refactored into distinct Serial and Parallel casters with SoA-driven SerialSimulation and ParallelSimulation. Trajectory storage consolidates to a single Trajectory. Motor6D pooling and ActiveCastSerial are added. Public API adds FastCast.new() (serial) and FastCast.newParallel() (parallel). Docs, README, benchmarks, and changelog updated.

Changes

Serial and Parallel Casting Architecture Refactor with SoA Simulation

Layer / File(s) Summary
Type Contracts & Trajectory Consolidation
src/FastCast2/TypeDefinitions.luau, src/FastCast2/DefaultConfigs.luau, src/FastCast2/Configs.luau, src/FastCast2/FastCastEnums.luau
CastStateInfo.TrajectoriesCastStateInfo.Trajectory; add default MovementMethod = "BulkMoveTo" and minor header/comment edits.
Main API & Constructors
src/FastCast2/init.luau
Split into FastCastSerial and FastCastParallel metatables; add FastCast.new() (serial) and FastCast.newParallel() (parallel); split behavior factories and per-mode method sets and lifecycles.
ActiveCast Core
src/FastCast2/ActiveCast.luau
ActiveCast now uses singular Trajectory; createCastData initializes StateInfo.Trajectory, sets RayInfo.FastCastEventsModule and RayInfo.MovementMethod, returns cast; fixes to DebrisAdd and high-fidelity substep timing.
ActiveCastSerial
src/FastCast2/ActiveCastSerial.luau
New serial cast object with kinematics helpers, RaycastParams cloning, debug visualization, and cast handlers.
BaseCastSerial Entrypoints
src/FastCast2/BaseCastSerial.luau
New serial base implementing Init, Raycast/Blockcast/Spherecast, builds castData, constructs ActiveCastSerial, registers with SerialSimulation, fires CastFire, and implements Bind/Terminate/Destroy and ObjectCache stub.
BaseCast Parallel Integration
src/FastCast2/BaseCast.luau
Parallel path updated to register created casts with ParallelSimulation.Register(cast), store actives by cast.ID, and fire CastFire using the local cast object.
ParallelSimulation (SoA)
src/FastCast2/ParallelSimulation.luau
New SoA-based ParallelSimulation: Register/Unregister, swap-compaction, per-step UpdateCasts deriving step ray directions, handling queries, updating cosmetics/Motor6D, queuing events, and Start/Stop wiring to RunService.
SerialSimulation (SoA)
src/FastCast2/SerialSimulation.luau
New SoA-driven SerialSimulation on Heartbeat: Register/Unregister, per-frame UpdateCasts advancing time, performing queries, updating cosmetic bullets (Motor6D supported), queuing events, and Start/Stop lifecycle.
Motor6D Pooling
src/FastCast2/Motor6DPool.luau
New Motor6D pooling module exposing Initialize, Get, Return, Connect, Disconnect and managing a single anchored pool for Transform movement.
Configs & Docs
src/FastCast2/Configs.luau, docs/*, README.md, TODO.md, skills/*
Add DebugLogging.AutomaticPerformance flag; add API reference, changelog 0.1.0, README Rojo/install and simplified examples, TODO updates, architecture/comparison/edge-case docs, and benchmark clients.
Benchmarks (Serial & Parallel)
Benchmarks/benchSerial.client.luau, Benchmarks/benchParallel.client.luau
Add benchmark clients sampling FPS and firing large numbers of casts (E for serial, P for parallel), tracking active casts and printing delta/average/max/min FPS summaries.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Bench as Benchmark Script
  participant Caster as FastCast Caster
  participant Sim as Serial/Parallel Simulation
  User->>Bench: press E or P
  Bench->>Caster: Fire AMOUNT casts
  Caster->>Sim: Register casts
  Sim->>Sim: process casts per frame
  Bench->>Caster: Terminate casts after BENCH_TIME
  Bench->>User: print FPS stats
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • Last major update plans #38 — Implements the refactor items described (Trajectories → Trajectory, serial/parallel casters, Motor6D pooling, HighFidelity fix, and API/docs changes).

Poem

🐰
I hopped the repo, snipped and sewn,
Split casters, made trajectories one,
Motor6Ds queued in tidy store,
Serial hums while Parallel soars,
FastCast leaps — I chew some more.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main change: introducing dual caster modes (serial and parallel) with a structure-of-arrays simulation architecture, which aligns with the extensive refactoring across multiple core modules.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch major

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
src/FastCast2/BaseCast.luau (1)

175-191: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Casts registered with ParallelSimulation but not stored in Actives table breaks cleanup and sync systems.

The cast created at lines 175-191 is registered with ParallelSimulation.Register(cast) but never added to the Actives table. However, Actives is still expected by:

  • HandleBulkMoveTo (line 56): Iterates Actives to batch-update cosmetic bullet positions
  • ActiveCastCleaner.Event (line 127): Expects Actives[activeCastID] to remove cleaned casts
  • SyncChanges.Event (line 139): Expects Actives[ID] to sync cast state
  • Destroy (line 368): Iterates Actives to terminate remaining casts

Additionally, ParallelSimulation.Terminate() does not fire the ActiveCastCleaner event when a cast completes. The cast object is passed both cleanup and sync event references during creation, but these events will never be triggered by the parallel simulation, leaving cleanup callbacks unreachable.

The same pattern exists in Blockcast (lines 238-255) and Spherecast (lines 282-299).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/FastCast2/BaseCast.luau` around lines 175 - 191, The new casts (created
in the Raycast/Blockcast/Spherecast paths using ActiveCast.createCastData and
then ParallelSimulation.Register(cast)) must be added into the Actives table and
have their completion trigger the existing cleanup/sync events; after you call
ParallelSimulation.Register(cast) add Actives[cast.ID] = cast, and ensure when
the parallel simulation signals a cast completion you fire
ActiveCastCleaner.Event(cast.ID) (and remove Actives[cast.ID]) and also fire
SyncChanges.Event(cast.ID) (or call the same cleanup callback path used
elsewhere) so ActiveCastCleaner, SyncChanges and Destroy/HandleBulkMoveTo logic
see and remove these casts; update the Raycast, Blockcast, and Spherecast blocks
accordingly and/or attach a termination callback from
ParallelSimulation.Register to invoke ActiveCastCleaner and SyncChanges for the
cast.
src/FastCast2/ActiveCast.luau (1)

367-380: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Finish the Trajectory rename inside SimulateCast().

These lines switch to local trajectory = cast.StateInfo.Trajectory, but the function still reads latestTrajectory later on. That variable is undefined here, so the first simulation step will nil-index on StartTime / Acceleration.

Suggested fix
 	local trajectory = cast.StateInfo.Trajectory
@@
-	totalDelta = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime
+	totalDelta = cast.StateInfo.TotalRuntime - trajectory.StartTime
@@
-	if typeof(latestTrajectory.Acceleration) ~= "Vector3" then
-		latestTrajectory.Acceleration = Vector3.new()
+	if typeof(trajectory.Acceleration) ~= "Vector3" then
+		trajectory.Acceleration = Vector3.new()
 	end

Also applies to: 416-418

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/FastCast2/ActiveCast.luau` around lines 367 - 380, In SimulateCast(), you
started using local trajectory = cast.StateInfo.Trajectory but left uses of
latestTrajectory later; replace every use of latestTrajectory with trajectory
(e.g., when computing totalDelta and accessing StartTime/Acceleration) so lines
that compute totalDelta, and any subsequent references (also around the block at
the other occurrence ~416-418) use trajectory.StartTime,
trajectory.Acceleration, etc.; verify GetPositionAtTime/GetVelocityAtTime calls
and TotalRuntime updates all reference trajectory instead of latestTrajectory.
README.md (1)

200-202: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use Hit, not RayHit, in the events module example.

The runtime lookup uses FastCastEvents.Hit; module.RayHit will never be invoked.

Suggested fix
-module.RayHit = function()
+module.Hit = function()
 	print("Hit!")
 end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 200 - 202, The example registers the wrong event
name; replace the handler assignment of module.RayHit with the runtime-expected
FastCastEvents.Hit by defining module.Hit (or assigning to
module[FastCastEvents.Hit]) so the runtime lookup calls it—update the example to
use module.Hit instead of module.RayHit and ensure the string/constant
FastCastEvents.Hit is referenced consistently with the rest of the module.
src/FastCast2/init.luau (2)

128-145: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Unify the StateInfo.Trajectory shape before using it here.

This file currently treats Trajectory as both an array (trajectories[index], table.insert(...)) and a single segment (trajectory.StartTime). It also writes through lastTrajectory, which is never defined in this scope. The first transform update on this path will hit a nil access or mutate the wrong structure.

Also applies to: 154-190

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/FastCast2/init.luau` around lines 128 - 145, GetTrajectoryInfo and
GetLatestTrajectoryEndInfo assume StateInfo.Trajectory is an array but elsewhere
it may be a single segment and code writes through lastTrajectory (undefined
here), causing nil/mutation bugs; fix by normalizing StateInfo.Trajectory to a
consistent array shape before use (e.g., if StateInfo.Trajectory is a map with
StartTime, wrap it in a one-element array) and update any writers to always
push/replace elements on that array, and remove or properly define
lastTrajectory (use the local variable trajectory or StateInfo.Trajectory[#]
instead) so GetTrajectoryInfo(GetLatestTrajectoryEndInfo) never indexes a
non-array or mutates the wrong structure.

645-651: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return after warning when the parallel caster is not initialized.

This guard only logs, then immediately calls self.Dispatcher:DispatchAll(...) anyway. If self.Dispatcher is nil, SetBulkMoveEnabled() still crashes on the next line.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/FastCast2/init.luau` around lines 645 - 651, In SetBulkMoveEnabled, the
guard only warns when the caster isn't initialized but proceeds to call
self.Dispatcher:DispatchAll and set self.BulkMoveEnabled, causing a crash if
self.Dispatcher is nil; update the function
(FastCastParallel:SetBulkMoveEnabled) to return immediately after the warn (or
otherwise check for self.Dispatcher) so DispatchAll and setting BulkMoveEnabled
only run when self.AlreadyInit and self.Dispatcher are valid.
🧹 Nitpick comments (2)
src/FastCast2/Motor6DPool.luau (1)

46-53: 💤 Low value

Consider simplifying Get() to avoid recursion.

The recursive call after GrowPool works correctly but could be simplified with a direct return.

Simplified version
 local function Get(): Motor6D
-	if `#FreeMotor6Ds` > 0 then
-		return table.remove(FreeMotor6Ds) :: Motor6D
-	else
+	if `#FreeMotor6Ds` == 0 then
 		GrowPool(PoolSize * GROWTH_RATE)
-		return Get()
 	end
+	return table.remove(FreeMotor6Ds) :: Motor6D
 end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/FastCast2/Motor6DPool.luau` around lines 46 - 53, The Get function
currently recurses after calling GrowPool; replace that recursion with direct
logic: call GrowPool(PoolSize * GROWTH_RATE) when FreeMotor6Ds is empty and then
immediately remove and return a Motor6D from FreeMotor6Ds. Update the Get
function to check FreeMotor6Ds, call GrowPool if empty, then perform a single
table.remove(FreeMotor6Ds) :: Motor6D return; keep references to FreeMotor6Ds,
GrowPool, PoolSize and GROWTH_RATE intact.
Benchmarks/benchSerial.client.luau (1)

55-70: 💤 Low value

Consider adding ProjectileContainer to filter exclusions.

The FilterDescendantsInstances is empty, but castBehavior.AutoIgnoreContainer = true should handle this. However, explicitly adding the container ensures it's excluded even before AutoIgnore processes it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Benchmarks/benchSerial.client.luau` around lines 55 - 70, The RaycastParams
FilterDescendantsInstances is left empty which can let the ProjectileContainer
be hit before AutoIgnoreContainer runs; update
CastParams.FilterDescendantsInstances to include the ProjectileContainer
instance so the container is explicitly excluded from raycasts (adjust the
CastParams used by castBehavior), i.e. add ProjectileContainer to
CastParams.FilterDescendantsInstances so CastParams (used by
castBehavior.RaycastParams) will proactively ignore that container.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Benchmarks/benchSerial.client.luau`:
- Around line 106-121: The current random direction uses integer
math.random(-1,1) which can produce a zero vector; update the Caster:RaycastFire
call to build float random components instead (e.g. x = (math.random() * 2 - 1)
* 5000) and normalize the Vector3 before passing it, and if the normalized
vector has zero magnitude replace it with a safe fallback direction (e.g.
Vector3.new(0,0,1)); apply these changes where Caster:RaycastFire is invoked so
the direction passed to RaycastFire is never a zero vector and maintains
expected length handling with the 35 parameter and castBehavior.

In `@README.md`:
- Around line 96-100: The README example uses FastCastEnums but never defines or
requires it, so copy-pasting fails; update the example to first require or
define FastCastEnums (the same module providing enums used by FastCast2) before
creating the behavior—e.g., add a line showing how to obtain FastCastEnums (the
module that exposes HighFidelityBehavior) and then keep the existing code that
calls FastCast2.newBehavior() and sets behavior.HighFidelityBehavior =
FastCastEnums.HighFidelityBehavior.Default so the snippet runs as-is.

In `@src/FastCast2/ActiveCast.luau`:
- Around line 854-857: High-fidelity branch still reads from latestTrajectory
instead of the new local trajectory, causing the crash in
HighFidelityBehavior.Always; update the Always branch to use the local variable
trajectory (from cast.StateInfo.Trajectory, where you already default
trajectory.Acceleration) when pulling origin/startTime/velocity/acceleration
(replace references to latestTrajectory in the HighFidelityBehavior.Always code
path with trajectory) so the branch uses the validated trajectory object.

In `@src/FastCast2/ActiveCastSerial.luau`:
- Around line 148-156: The RayInfo table in ActiveCastSerial.luau doesn't copy
shape fields, so SerialSimulation.Register (which reads cast.RayInfo.Size and
cast.RayInfo.Radius for Blockcast/Spherecast) sees nil; update the constructor
that builds RayInfo to include Size = castData.Size and Radius = castData.Radius
(or the appropriate source names on castData) so Blockcast and Spherecast have
their shape parameters when Type is set via CastVariantTypes[castData.CastType];
ensure the names match what SerialSimulation.Register expects.

In `@src/FastCast2/BaseCastSerial.luau`:
- Around line 32-45: The module-level mutable state (Output, ParentCaster,
ObjectCache, BulkMoveToConnection, NextProjectileID) must be made instance-local
so multiple BaseCastSerial instances do not share or clobber each other; modify
BaseCastSerial.Init to set self.Output, self.ParentCaster, self.ObjectCache,
self.BulkMoveToConnection and self.NextProjectileID (and initialize them
appropriately) instead of writing the module-scope variables, then update all
functions in BaseCastSerial that currently reference Output, ParentCaster,
ObjectCache, BulkMoveToConnection or NextProjectileID to use self.Output,
self.ParentCaster, self.ObjectCache, self.BulkMoveToConnection and
self.NextProjectileID so every caster keeps its own event sink/cache/parent and
ID counter.

In `@src/FastCast2/init.luau`:
- Around line 919-935: FastCastSerial:TerminateCast currently only disconnects
local state and destroys cosmetic objects but never delegates termination to the
base serial, leaving the cast registered; update FastCastSerial:TerminateCast to
call self.BaseCast:TerminateCast(cast, castTerminatingFunction) (instead of
directly invoking castTerminatingFunction) after performing the local cleanup
(disconnecting UpdateConnection and destroying CosmeticBulletObject) and before
firing self.Output:Fire("CastTerminating", cast) so the cast is properly
deregistered from SerialSimulation/BaseCast.
- Around line 815-818: GetVelocityCast in FastCastSerial currently returns the
trajectory's InitialVelocity, which ignores acceleration and elapsed time;
update GetVelocityCast(cast: vaildcast) to compute velocity at runtime by using
cast.StateInfo.Trajectory and the elapsed time (use TotalRuntime - StartTime
from the same cast.StateInfo like the position helper does), then evaluate the
trajectory's velocity at that elapsed time and return it instead of
InitialVelocity so accelerating serial casts report the current velocity.
- Around line 847-903: The setters/adders (SetVelocityCast, SetAccelerationCast,
SetPositionCast, AddPositionCast, AddVelocityCast, AddAccelerationCast) mutate
cast.StateInfo.Trajectory in-place without advancing StartTime to the cast's
TotalRuntime, causing retroactive path changes; before changing
Origin/InitialVelocity/Acceleration you must "rebase" the serial trajectory by
integrating the current Trajectory forward to cast.StateInfo.TotalRuntime
(compute the new Origin at that time), set Trajectory.StartTime =
cast.StateInfo.TotalRuntime, and replace
Trajectory.Origin/InitialVelocity/Acceleration with values relative to that new
StartTime (or add deltas for the Add* methods) so subsequent solves treat the
change as occurring only from the current runtime onward. Ensure this rebase
logic is invoked from each of the listed methods (or factor into a helper like
RebaseSerialTrajectory(cast) and call it first).
- Around line 1034-1044: The constructors FastCast.new and FastCast.newParallel
build caster tables (e.g., local fs = { ... } and local fs = { ... }) and set
their metatables (setmetatable(fs, FastCastSerial) / setmetatable(fs,
FastCastParallel)), but currently don't return them; update both functions to
return the constructed fs at the end so callers receive the caster instance
(ensure return fs is added after setmetatable in both FastCast.new and
FastCast.newParallel).
- Around line 721-733: The serial BindableEvent created as BindableOutput is not
wired to the public signals after calling BaseCastSerial.Init, so events like
"CastFire" and "CastTerminating" emitted by self.BaseCast never reach consumers;
add a listener on BindableOutput.Event (after self.BaseCast =
BaseCastSerial.Init(...)) that inspects the incoming event name and forwards it
to the corresponding public signal on this instance (for example, map "CastFire"
-> self.CastFire:Fire(...), "CastTerminating" -> self.CastTerminating:Fire(...),
and forward any other serial event names similarly) so that
FastCastSerial:Init/instance exposes the same callbacks as BaseCastSerial.

In `@src/FastCast2/ParallelSimulation.luau`:
- Around line 263-270: The call to CastHandler is passing a table named variant
for all cast types but Blockcast expects a Vector3 and Spherecast expects a
number; change how variant is constructed so that for EnumCastTypes.Blockcast
you set variant to castSize[i] (a Vector3) and for EnumCastTypes.Spherecast set
variant to castRadius[i] (a number), leaving the existing table form only for
other cast types if needed, then call CastHandler(castWorldRoot[i],
lastPosition, rayDir, castRaycastParams[i], variant).
- Around line 313-316: The loop that calls ParallelSimulation.Terminate over
destroyedIds must iterate in descending numeric order because Unregister() uses
swap-delete and removing lower indices first can change positions of higher
indices; update the code that processes destroyedIds (the block calling
ParallelSimulation.Terminate) to sort or iterate destroyedIds from highest to
lowest (e.g., iterate numeric indices in reverse) so Terminate is invoked on IDs
in descending order to avoid invalidating queued ids after Unregister().
- Around line 161-221: The removal logic swaps the tail (lastId) into slot id
but then disconnects and clears fields from id (corrupting the moved cast); fix
by treating lastId as the source: when id ~= lastId assign casts[id] =
casts[lastId] (in addition to the per-field arrays) and update casts[lastId].ID
= id, then call Motor6DPool.Disconnect(castMotor6D[lastId]) and nil out all
cast*[...] entries using lastId (not id) before decrementing castCount; ensure
castCFrame and castMotor6D clears reference lastId so the moved cast in slot id
remains intact.
- Around line 321-324: ParallelSimulation.Start currently connects RS.PreRender
(client-only) which will error on servers; modify ParallelSimulation.Start to
first check the RunService client/server state (use RS:IsClient() or similar)
and only call RS.PreRender:ConnectParallel(UpdateCasts) when running on the
client, or alternatively use RS.Heartbeat for server-side behavior; ensure you
still set and guard ParallelSimulation.StepConnection and early-return if
already connected. This change keeps ParallelSimulation.Start, UpdateCasts, and
ParallelSimulation.StepConnection as the anchors for the fix.

In `@src/FastCast2/SerialSimulation.luau`:
- Around line 317-320: The loop that calls SerialSimulation.Terminate for each
id in destroyedIds must iterate destroyedIds in descending index order to avoid
invalidation caused by swap-delete compaction; change the iteration over
destroyedIds so you process highest indices first (e.g., iterate from
`#destroyedIds` down to 1) and call SerialSimulation.Terminate(id) in that
descending order to ensure removals do not shift and invalidate later ids.
- Around line 263-274: The code always passes the variant table to CastHandler,
but Blockcast and Spherecast handlers expect a raw Vector3/number; update the
call so it passes variant.Size for EnumCastTypes.Blockcast and variant.Radius
for EnumCastTypes.Spherecast (use castSize[i] and castRadius[i] or the variant
fields you already set) instead of the whole variant table when castType is
Blockcast or Spherecast; keep passing variant only for other cast types. Ensure
this change is applied at the CastHandler(...) invocation so the correct shape
argument is forwarded to block/sphere handlers.
- Around line 164-225: The code in Unregister swaps the tail row into id but
then disconnects and clears entries at index id (which now holds the moved
cast), causing the moved cast to be corrupted; change the cleanup to operate on
lastId instead: perform Motor6DPool.Disconnect(castMotor6D[lastId]) and set
castIDs[lastId], castOrigin[lastId], ..., castCFrame[lastId],
castMotor6D[lastId], casts[lastId] = nil, then set castCount = lastId - 1. Keep
the earlier copy/ID update that moves lastId into id, but ensure all nil
assignments and the disconnect target lastId (not id) so the moved cast at id
remains intact.

---

Outside diff comments:
In `@README.md`:
- Around line 200-202: The example registers the wrong event name; replace the
handler assignment of module.RayHit with the runtime-expected FastCastEvents.Hit
by defining module.Hit (or assigning to module[FastCastEvents.Hit]) so the
runtime lookup calls it—update the example to use module.Hit instead of
module.RayHit and ensure the string/constant FastCastEvents.Hit is referenced
consistently with the rest of the module.

In `@src/FastCast2/ActiveCast.luau`:
- Around line 367-380: In SimulateCast(), you started using local trajectory =
cast.StateInfo.Trajectory but left uses of latestTrajectory later; replace every
use of latestTrajectory with trajectory (e.g., when computing totalDelta and
accessing StartTime/Acceleration) so lines that compute totalDelta, and any
subsequent references (also around the block at the other occurrence ~416-418)
use trajectory.StartTime, trajectory.Acceleration, etc.; verify
GetPositionAtTime/GetVelocityAtTime calls and TotalRuntime updates all reference
trajectory instead of latestTrajectory.

In `@src/FastCast2/BaseCast.luau`:
- Around line 175-191: The new casts (created in the
Raycast/Blockcast/Spherecast paths using ActiveCast.createCastData and then
ParallelSimulation.Register(cast)) must be added into the Actives table and have
their completion trigger the existing cleanup/sync events; after you call
ParallelSimulation.Register(cast) add Actives[cast.ID] = cast, and ensure when
the parallel simulation signals a cast completion you fire
ActiveCastCleaner.Event(cast.ID) (and remove Actives[cast.ID]) and also fire
SyncChanges.Event(cast.ID) (or call the same cleanup callback path used
elsewhere) so ActiveCastCleaner, SyncChanges and Destroy/HandleBulkMoveTo logic
see and remove these casts; update the Raycast, Blockcast, and Spherecast blocks
accordingly and/or attach a termination callback from
ParallelSimulation.Register to invoke ActiveCastCleaner and SyncChanges for the
cast.

In `@src/FastCast2/init.luau`:
- Around line 128-145: GetTrajectoryInfo and GetLatestTrajectoryEndInfo assume
StateInfo.Trajectory is an array but elsewhere it may be a single segment and
code writes through lastTrajectory (undefined here), causing nil/mutation bugs;
fix by normalizing StateInfo.Trajectory to a consistent array shape before use
(e.g., if StateInfo.Trajectory is a map with StartTime, wrap it in a one-element
array) and update any writers to always push/replace elements on that array, and
remove or properly define lastTrajectory (use the local variable trajectory or
StateInfo.Trajectory[#] instead) so
GetTrajectoryInfo(GetLatestTrajectoryEndInfo) never indexes a non-array or
mutates the wrong structure.
- Around line 645-651: In SetBulkMoveEnabled, the guard only warns when the
caster isn't initialized but proceeds to call self.Dispatcher:DispatchAll and
set self.BulkMoveEnabled, causing a crash if self.Dispatcher is nil; update the
function (FastCastParallel:SetBulkMoveEnabled) to return immediately after the
warn (or otherwise check for self.Dispatcher) so DispatchAll and setting
BulkMoveEnabled only run when self.AlreadyInit and self.Dispatcher are valid.

---

Nitpick comments:
In `@Benchmarks/benchSerial.client.luau`:
- Around line 55-70: The RaycastParams FilterDescendantsInstances is left empty
which can let the ProjectileContainer be hit before AutoIgnoreContainer runs;
update CastParams.FilterDescendantsInstances to include the ProjectileContainer
instance so the container is explicitly excluded from raycasts (adjust the
CastParams used by castBehavior), i.e. add ProjectileContainer to
CastParams.FilterDescendantsInstances so CastParams (used by
castBehavior.RaycastParams) will proactively ignore that container.

In `@src/FastCast2/Motor6DPool.luau`:
- Around line 46-53: The Get function currently recurses after calling GrowPool;
replace that recursion with direct logic: call GrowPool(PoolSize * GROWTH_RATE)
when FreeMotor6Ds is empty and then immediately remove and return a Motor6D from
FreeMotor6Ds. Update the Get function to check FreeMotor6Ds, call GrowPool if
empty, then perform a single table.remove(FreeMotor6Ds) :: Motor6D return; keep
references to FreeMotor6Ds, GrowPool, PoolSize and GROWTH_RATE intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a57e535-492e-4838-826f-86da925a99ab

📥 Commits

Reviewing files that changed from the base of the PR and between b6ae671 and 82519af.

📒 Files selected for processing (18)
  • Benchmarks/benchParallel.client.luau
  • Benchmarks/benchSerial.client.luau
  • README.md
  • TODO.md
  • docs/api-reference.md
  • docs/changelog.md
  • src/FastCast2/ActiveCast.luau
  • src/FastCast2/ActiveCastSerial.luau
  • src/FastCast2/BaseCast.luau
  • src/FastCast2/BaseCastSerial.luau
  • src/FastCast2/Configs.luau
  • src/FastCast2/DefaultConfigs.luau
  • src/FastCast2/FastCastEnums.luau
  • src/FastCast2/Motor6DPool.luau
  • src/FastCast2/ParallelSimulation.luau
  • src/FastCast2/SerialSimulation.luau
  • src/FastCast2/TypeDefinitions.luau
  • src/FastCast2/init.luau

Comment thread Benchmarks/benchSerial.client.luau
Comment thread README.md
Comment thread src/ActiveCast.luau Outdated
Comment thread src/ActiveCastSerial.luau Outdated
Comment thread src/FastCast2/BaseCastSerial.luau Outdated
Comment thread src/FastCast2/ParallelSimulation.luau Outdated
Comment thread src/ParallelSimulation.luau Outdated
Comment thread src/SerialSimulation.luau Outdated
Comment thread src/SerialSimulation.luau Outdated
Comment thread src/FastCast2/SerialSimulation.luau Outdated
@weenachuangkud weenachuangkud self-assigned this May 8, 2026
@weenachuangkud weenachuangkud added documentation Improvements or additions to documentation enhancement New feature or request labels May 8, 2026
…astParallel

- Add SoA pattern with full cast data arrays (Paused, TotalRunTime, Trajectory, etc.)
- Implement Register/Unregister/SetMovementMode/QueueEvent methods
- Add GetSoA() to expose arrays for user-implemented UpdateCasts
- Add GetBaseCastRef() to access ObjectCache/Motor6DCache methods
- Fix BulkMoveToConnection undefined reference in Destroy()
- Add Motor6DCache require and instance management
- Add BindMotor6DCache, _GetMotor6D, _ReturnMotor6D methods
- Update SetMovementMode to delegate to ParallelSimulation
- Add FireQueuedEvents for event handling after sync
- Init now starts ParallelSimulation and connects RunService
…on SoA

- Update FireQueuedEvents to use castID + args (varargs) from ParallelSimulation
- Update HandleBulkMoveTo/HandleMotor6D to get data from GetSoA()
- Add Start()/Stop() methods to ParallelSimulation for RunService connection
- Add ParallelSimulation.Connection for connection management
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant