diff --git a/.gitignore b/.gitignore index 475f38af..0b45067a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,8 @@ # Moonwave related stuff /node_modules -/package-lock.json \ No newline at end of file +/package-lock.json + +# FastCast2 related stuff +/src/*.legacy.luau +.aider* diff --git a/.vscode/settings.json b/.vscode/settings.json index 3386996b..3eab95c6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,5 @@ "[luau]": { "editor.defaultFormatter": "JohnnyMorganz.luau-lsp", }, - "stylua.targetReleaseVersion": "latest", "luau-lsp.studioPlugin.enabled": true } \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..3ea8d1a4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,56 @@ +# AGENTS.md + +## Project Overview + +FastCast2 is a Roblox projectile library written in Luau, providing high-performance raycasting, blockcasting, and spherecasting with parallel scripting support. It is an unofficial continuation of the original FastCast library. + +- **Language**: Luau (Roblox) +- **Build Tool**: Rojo (`rojo sync`, `rojo serve`) +- **Documentation**: Moonwave +- **Repository**: https://github.com/weenachuangkud/FastCast2 + +## Development Commands + +- **Sync to Roblox**: `rojo sync -o ` +- **Serve live**: `rojo serve` (then connect via Studio → Plugins → Rojo) +- **Build docs**: `moonwave build` +- **Publish docs**: `moonwave build --publish` + +## Project Structure + +``` +src/ +├── init.luau # Entry: FastCast (static), FastCastSerial, FastCastParallel +├── BaseCastSerial.luau # Serial: cast handler, routes events to SerialSimulation +├── BaseCastParallel.luau # Parallel: runs inside each Actor VM, casts per-VM +├── SerialSimulation.luau # Serial: SoA physics engine, single-threaded +├── ParallelSimulation.luau # Parallel: SoA physics engine, one per Actor VM +├── ActiveCast.luau # Cast data container (used by both modes) +├── ObjectCache.luau # Cosmetic bullet part pooling +├── Motor6DCache.luau # Motor6D pooling for Transform movement mode +├── TypeDefinitions.luau # All Luau type definitions +├── FastCastEnums.luau # Enum values (HighFidelityBehavior, CastType) +├── Config.luau # Debug logging flags +├── DefaultConfigs.luau # Default FastCastBehavior values +└── FastCastVMs/ + ├── init.luau # Dispatcher: creates/manages Actor VMs, load balancing + ├── ClientVM.client.luau # Client-side Actor script + ├── ServerVM.server.luau # Server-side Actor script + └── *.meta.json # Rojo metadata (Enabled = false) +``` + +## Testing + +There are no automated tests. Testing is manual via Roblox Studio. + +## Code Style + +- Luau static typing throughout +- PascalCase for types, camelCase for variables/functions +- Two-space indentation +- SoA (Structure of Arrays) pattern for simulation data + +## Agent Skills + +To understand specific project workflows, refer to the skills defined here: +- @skills/architecture.md diff --git a/Benchmarks/bench1.client.luau b/Benchmarks/bench1.client.luau deleted file mode 100644 index 991b4021..00000000 --- a/Benchmarks/bench1.client.luau +++ /dev/null @@ -1,154 +0,0 @@ --- Services -local RS = game:GetService("RunService") -local Rep = game:GetService("ReplicatedStorage") -local UIS = game:GetService("UserInputService") -local RepFirst = game:GetService("ReplicatedFirst") - --- Requires -local FastCastM = require(Rep:WaitForChild("FastCast2")) - --- Variables -local ProjectileContainer = Instance.new("Folder") -ProjectileContainer.Name = "FastCast2PJ" -ProjectileContainer.Parent = workspace -local ProjectileTemplate = Instance.new("Part") -ProjectileTemplate.Name = "Projectile" -ProjectileTemplate.Parent = Rep -ProjectileTemplate.Size = Vector3.new(1,1,1) -ProjectileTemplate.CanCollide = false -ProjectileTemplate.Anchored = true -ProjectileTemplate.CanQuery = false -ProjectileTemplate.CanTouch = false -ProjectileTemplate.Position = Vector3.new(1,1,1) -ProjectileTemplate.Massless = true - --- FPS rs -local start = tick() -local updateRate = 0.5 -local fpsTable = {} -local averageFps = 0 -local maxFps = 0 -local minFps = 0 -local delta = 0 - -RS.Heartbeat:Connect(function(dt: number) - local fps = 1/dt -- NOTE: You can math.floor this - if fps > maxFps then - maxFps = fps - end - - if fps < minFps then - minFps = fps - end - - delta = fps - table.insert(fpsTable, fps) - - if tick() >= start + updateRate then - local totalFps = 0 - for _, vFps in fpsTable do - totalFps += vFps - end - - averageFps = totalFps / #fpsTable - fpsTable = {} - start = tick() - end -end) - --- CastParams -local CastParams = RaycastParams.new() -CastParams.FilterDescendantsInstances = {character} -CastParams.FilterType = Enum.RaycastFilterType.Exclude -CastParams.IgnoreWater = true - --- Behavior -local castBehavior = FastCastM.newBehavior() -castBehavior.MaxDistance = 999999999 -castBehavior.RaycastParams = CastParams -castBehavior.HighFidelityBehavior = 1 -castBehavior.HighFidelitySegmentSize = 1 -castBehavior.Acceleration = Vector3.new(0, 0, 0) -castBehavior.AutoIgnoreContainer = true -castBehavior.CosmeticBulletContainer = ProjectileContainer -castBehavior.CosmeticBulletTemplate = ProjectileTemplate -for i, v in castBehavior.FastCastEventsConfig do - v = false -end - -for i, v in castBehavior.FastCastEventsModuleConfig do - v = false -end -castBehavior.FastCastEventsConfig.UseCastFire = true - - --- Caster -local activeCasts = {} -local Caster = FastCastM.new() -Caster:Init( - 4, - RepFirst, - "CastVMs", - RepFirst, - "CastVMContainer", - "CastVM", - true -) -Caster.CastFire = function(cast) - table.insert(activeCasts, cast) -end - --- Functions -local function summary() - print("Delta: " .. tostring(delta)) - print("Average fps: " .. tostring(averageFps)) - print("Max fps: " .. tostring(maxFps)) - print("Min fps: " .. tostring(minFps)) -end - --- Benchmark -local isBenchmarking = false -local AMOUNT = 5000 -local BENCH_TIME = 5 -local VEC = 35 - -UIS.InputBegan:Connect(function(input, gp) - if gp then return end - if isBenchmarking then return end - if input.KeyCode == Enum.KeyCode.E then - isBenchmarking = true - for i = 1, AMOUNT do - Caster:RaycastFire( - Vector3.new( - math.random(-1, 1) - 5000 - math.random(-1, 1) - ), - Vector3.new( - math.random(-1, 1) - 5000, - math.random(-1, 1) - ), - VEC, - castBehavior - ) - end - print("Creating activeCasts") - print("--------------------") - summary() - print("--------------------") - task.wait(BENCH_TIME) - print("Simulate") - print("--------------------") - summary() - print("--------------------") - for i, v in activeCasts do - FastCastM:TerminateCast(v) - end - print("DESTROY") - print("--------------------") - summary() - print("--------------------") - isBenchmarking = false - end -end) diff --git a/README.md b/README.md index d1fadee4..2170db33 100644 --- a/README.md +++ b/README.md @@ -63,24 +63,39 @@ Read more on [FastCast2 devforum](https://devforum.roblox.com/t/fastcast2-an-imp --- +## Install with Rojo + +1. Install the [Rojo CLI](https://rojo.space/docs/installation/) for your system. +2. Clone this repository: + ```bash + git clone https://github.com/weenachuangkud/FastCast2.git + cd FastCast2 + rm -rf .git + ``` +3. Sync to Roblox: + ```bash + rojo sync -o + ``` + Or serve live with: + ```bash + rojo serve + ``` + Then connect in Roblox Studio via **Studio → Plugins → Rojo**. + +--- + # Code example -Shooting projectiles from your head +Shooting projectiles from your head (Serial mode - simpler, main thread): ```lua -- Services local Rep = game:GetService("ReplicatedStorage") -local RepFirst = game:GetService("ReplicatedFirst") local Players = game:GetService("Players") -local UIS = game:GetService("UserInputService") - --- Modules -local FastCast2 = Rep:WaitForChild("FastCast2") -- Requires -local FastCastTypes = require(FastCast2:WaitForChild("TypeDefinitions")) -local FastCastEnums = require(FastCast2:WaitForChild("FastCastEnums")) -local FastCastM = require(FastCast2) +local FastCast2 = require(Rep:WaitForChild("FastCast2")) +local FastCastEnums = require(Rep:WaitForChild("FastCast2"):WaitForChild("FastCastEnums")) -- CONSTANTS local SPEED = 500 @@ -89,93 +104,74 @@ local SPEED = 500 local player = Players.LocalPlayer local character = player.Character or player.CharacterAdded:Wait() local Head = character:WaitForChild("Head") - local Mouse = player:GetMouse() local ProjectileContainer = workspace:WaitForChild("Projectiles") local ProjectileTemplate = Rep:WaitForChild("Projectile") -local debounce = false -local debounce_time = 0.05 - -- CastParams local CastParams = RaycastParams.new() CastParams.FilterDescendantsInstances = {character} CastParams.FilterType = Enum.RaycastFilterType.Exclude CastParams.IgnoreWater = true --- Behavior -local castBehavior: FastCastTypes = FastCastM.newBehavior() -castBehavior.MaxDistance = 1000 -castBehavior.RaycastParams = CastParams -castBehavior.HighFidelityBehavior = FastCastEnums.HighFidelityBehavior.Default -castBehavior.HighFidelitySegmentSize = 1 -castBehavior.Acceleration = Vector3.new(0, -workspace.Gravity/2.3, 0) -castBehavior.AutoIgnoreContainer = true -castBehavior.CosmeticBulletContainer = ProjectileContainer -castBehavior.CosmeticBulletTemplate = ProjectileTemplate -castBehavior.UserData = {} -- Initial UserData when ActiveCast created -castBehavior.FastCastEventsConfig = { - UseHit = true, - UseLengthChanged = false, -- Warning: Setting this to true will make your FPS tank when there are 100-200+ projectiles - UseCastTerminating = true, - UseCastFire = true, - UsePierced = false -} - --- Caster -local Caster = FastCastM.new() -Caster:Init( - 4, - RepFirst, - "CastVMs", - RepFirst, - "CastVMContainer", - "CastVM", - true -) +-- Behavior (FastCastBehavior) +local behavior = FastCast2.newBehavior() +behavior.MaxDistance = 1000 +behavior.RaycastParams = CastParams +behavior.HighFidelityBehavior = FastCastEnums.HighFidelityBehavior.Default +behavior.HighFidelitySegmentSize = 1 +behavior.Acceleration = Vector3.new(0, -workspace.Gravity/2.3, 0) +behavior.CosmeticBulletContainer = ProjectileContainer +behavior.CosmeticBulletTemplate = ProjectileTemplate --- Functions -local function OnCastTerminating(cast: FastCastTypes.ActiveCastData) - local obj = cast.RayInfo.CosmeticBulletObject - if obj then - obj:Destroy() - end -end +-- Serial Caster (runs on main thread, simpler) +local Caster = FastCast2.new() +Caster:Init("BulkMoveTo", false) -- movementMode, useObjectCache -local function OnHit() - print("Hit!") +-- Events (can be set before Init) +Caster.Hit = function(cast, result, velocity, bullet) + print("Hit: " .. result.Instance.Name) end -local function OnCastFire() - print("CastFire!") +-- Fire +local function fire() + local origin = Head.Position + local direction = (Mouse.Hit.Position - origin).Unit + Caster:RaycastFire(origin, direction, SPEED, behavior) end --- Connections - -Caster.CastTerminating:Connect(OnCastTerminating) -Caster.Hit:Connect(OnHit) -Caster.CastFire:Connect(OnCastFire) - -UIS.InputBegan:Connect(function(Input: InputObject, gp: boolean) - if gp then return end - if debounce then return end - - if Input.UserInputType == Enum.UserInputType.MouseButton1 then - debounce = true - - local Origin = Head.Position - local Direction = (Mouse.Hit.Position - Origin).Unit - - Caster:RaycastFire(Origin, Direction, SPEED, castBehavior) - - task.wait(debounce_time) - debounce = false +-- Input +game:GetService("UserInputService").InputBegan:Connect(function(input, gameProcessed) + if gameProcessed then return end + if input.UserInputType == Enum.UserInputType.MouseButton1 then + fire() end end) ``` +Parallel mode (for high-performance with multiple VMs): + +```lua +-- Parallel Caster (requires Init with worker count) +local Caster = FastCast2.newParallel() +Caster:Init( + 4, -- numWorkers + workspace, -- VM folder parent + "FastCastVMs", -- VM folder name + workspace, -- container parent + "VMContainer", -- container name + "VM", -- VM name + "BulkMoveTo", -- movementMode + nil, -- FastCastEventsModule (optional) + false -- useObjectCache +) + +-- Fire the same as serial +Caster:RaycastFire(origin, direction, SPEED, behavior) +``` +
How to set up [FastCastEventsModule](https://weenachuangkud.github.io/FastCast2/api/TypeDefinitions/#FastCastEventsModule) @@ -216,7 +212,7 @@ module.CastTerminating = function() print("CastTerminating!") end -module.RayHit = function() +module.Hit = function() print("Hit!") end @@ -237,13 +233,13 @@ end return module ``` -After this, add this piece of code below the `FastCast:Init(...)`: +Register it on your parallel caster after `Init`: ```lua - Caster:SetFastCastEventsModule(pathTo.FastCastEventsModule) +Caster:SetFastCastEventsModule(pathTo.FastCastEventsModule) ``` -(FastCastEventsModule can be used to optimize some FastCastEvents, like LengthChanged) +> **Note**: `SetFastCastEventsModule` is only available on parallel casters. In serial mode, set event handlers directly on the caster (e.g., `Caster.Hit = function(...)`). ### -> Get started with the [FastCast2 documentation](https://weenachuangkud.github.io/FastCast2/) diff --git a/TODO.md b/TODO.md index ef307dd7..401992e5 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,14 @@ -- [ ] Support both parallel and non-parallel modes - - FastCast2 currently has parallel Luau overhead; it should instead let users choose -- [ ] ActiveCast should expose AoS to users while internally using SoA for performance -- [ ] Consider using one RunService per core instead of multiple instances -- [ ] Add Motor6D transform feature -- [ ] Update documentation -- [ ] Add benchmarks -- [ ] Refactor +- [x] Support both parallel/nonParallel + - FastCast.new() for Serial, FastCast.newParallel() for Parallel +- [x] ActiveCast should exposes AoS to users, and Internally using SoA for performances + - ActiveCast uses pure data structures (AoS), SerialSimulation/ParallelSimulation use SoA internally +- [x] Consider using 1 RunService for each cores instead of using multiple + - Serial: 1 global RunService for all casts + - Parallel: 1 RunService per Actor +- [x] Add Motor6D Transform feature + - MovementMethod in FastCastBehavior (BulkMoveTo/Transform) + - Motor6DPool for efficient pooling +- [x] Fix HighFidelityBehavior = 2 bug - subRayDir used delta instead of timeIncrement +- [x] ActiveCast.Trajectories -> ActiveCast.Trajectory +- [x] Documentation updates +- [x] Add benchmarks \ No newline at end of file diff --git a/default.project.json b/default.project.json index 51163881..528f63db 100644 --- a/default.project.json +++ b/default.project.json @@ -4,8 +4,9 @@ "$className": "DataModel", "ReplicatedStorage": { "FastCast2": { - "$path": "src/FastCast2" - } + "$path": "src" + }, + "$ignoreUnknownInstances": true } } } \ No newline at end of file diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 00000000..4580ea1c --- /dev/null +++ b/docs/api-reference.md @@ -0,0 +1,303 @@ +# FastCast2 Documentation + +## 1. Caster + +### 1.1 Serial Mode (`FastCast.new()`) + +Serial Caster runs all cast simulations on the main thread. Simpler to use but less performant than Parallel. + +```lua +local caster = FastCast2.new() +caster:Init(movementMode, useObjectCache, template, cacheSize, cacheHolder) +``` + +#### 1.1.1 Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `movementMode` | `"BulkMoveTo" \| "Motor6D"` | How cosmetic bullets are moved each frame | +| `useObjectCache` | `boolean` | Enable part pooling via ObjectCache | +| `Template` | `BasePart \| Model?` | Template for ObjectCache | +| `CacheSize` | `number?` | Pre-allocated cache size (default 500) | +| `CacheHolder` | `Instance?` | Parent for cached objects (default workspace) | + +#### 1.1.2 Events + +Events are assigned directly on the caster **before or after Init**: + +```lua +caster.Hit = function(cast, result, velocity, cosmeticBullet) end +caster.Pierced = function(cast, result, velocity, cosmeticBullet) end +caster.LengthChanged = function(cast, lastPoint, rayDir, rayDisplacement) end +caster.CastFire = function(cast, origin, direction, velocity, behavior) end +caster.CastTerminating = function(cast) end +caster.CanPierce = function(cast, result, velocity, cosmeticBullet) -> boolean end +``` + +#### 1.1.3 Movement Modes + +- **`"BulkMoveTo"`** — Uses `workspace:BulkMoveTo()` each frame. Good for many projectiles. +- **`"Motor6D"`** — Uses Motor6D instances (Transform property). Better visual smoothing. + +Switch modes at runtime with `caster:SetMovementMode(mode)`. + +#### 1.1.4 ObjectCache + +ObjectCache reuses projectile parts for better performance: + +```lua +caster:Init("BulkMoveTo", true, projectileTemplate, 500, workspace) +``` + +--- + +### 1.2 Parallel Mode (`FastCast.newParallel()`) + +Parallel Caster runs cast simulations on separate Actor VMs for high-performance scenarios. + +```lua +local caster = FastCast2.newParallel() +caster:Init( + numWorkers, -- number of worker VMs (must be > 1) + newParent, -- Folder to place FastCastVMs + newName, -- name for FastCastVMs folder + ContainerParent, -- parent for worker containers + VMContainerName, -- name for containers + VMname, -- name for each worker VM + movementMode, -- "BulkMoveTo" or "Motor6D" + fastCastEventsModule,-- optional ModuleScript + useObjectCache, -- enable ObjectCache + template, -- ObjectCache template + cacheSize, -- ObjectCache size + CacheHolder -- ObjectCache parent +) +``` + +#### 1.2.1 Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `numWorkers` | `number` | Number of Actor VMs. Must be > 1. | +| `newParent` | `Folder` | Parent for the FastCastVMs Folder | +| `newName` | `string` | Name for the FastCastVMs Folder | +| `ContainerParent` | `Folder` | Parent for worker VM Containers | +| `VMContainerName` | `string` | Name for VM Containers | +| `VMname` | `string` | Name given to each worker VM | +| `movementMode` | `"BulkMoveTo" \| "Motor6D"` | Movement method | +| `fastCastEventsModule` | `ModuleScript?` | FastCastEvents module (see §4.1) | +| `useObjectCache` | `boolean` | Enable ObjectCache | +| `template` | `BasePart \| Model?` | ObjectCache template | +| `cacheSize` | `number?` | ObjectCache size | +| `CacheHolder` | `Instance?` | ObjectCache parent | + +#### 1.2.2 How It Works + +- Creates Actor-based worker VMs using VMsDispatcher +- Each worker handles multiple casts in parallel via `ConnectParallel` +- Fire requests are round-robined to workers for load balancing + +#### 1.2.3 Configuration Methods + +```lua +caster:SetFastCastEventsModule(moduleScript) -- Parallel only +caster:SetMovementMode(mode, enabled) +caster:SetObjectCacheEnabled(enabled, template?, cacheSize?, cacheHolder?) +``` + +--- + +### 1.3 Fire Methods + +All three casters share the same fire interface: + +```lua +caster:RaycastFire(origin, direction, velocity, BehaviorData?) +caster:BlockcastFire(origin, Size, direction, velocity, BehaviorData?) +caster:SpherecastFire(origin, Radius, direction, velocity, BehaviorData?) +``` + +- `velocity` can be a `Vector3` (exact velocity) or `number` (speed in the fire direction) +- `BehaviorData` is a `FastCastBehavior` created with `FastCast2.newBehavior()` + +--- + +### 1.4 Cast Manipulation (static methods on `FastCast`) + +```lua +-- Getters +FastCast.GetPositionCast(cast) → Vector3 +FastCast.GetVelocityCast(cast) → Vector3 +FastCast.GetAccelerationCast(cast) → Vector3 + +-- Setters (modifies trajectory, triggers CancelHighResCast) +FastCast.SetVelocityCast(cast, velocity) → () +FastCast.SetAccelerationCast(cast, acceleration) → () +FastCast.SetPositionCast(cast, position) → () + +-- Adders (relative modification) +FastCast.AddPositionCast(cast, position) → () +FastCast.AddVelocityCast(cast, velocity) → () +FastCast.AddAccelerationCast(cast, acceleration) → () + +-- Termination +FastCast.TerminateCast(cast) → () +``` + +In **parallel mode**, call `caster:SyncChangesToCast(cast)` after any Set/Add to push state to the worker VM. + +--- + +### 1.5 Lifecycle + +```lua +caster:Destroy() -- Cleans up all resources, actors, caches +``` + +--- + +## 2. FastCastBehavior + +Created via `FastCast2.newBehavior()`. Configuration for cast behavior: + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `RaycastParams` | `RaycastParams?` | `nil` | Filter rules for raycasting | +| `MaxDistance` | `number` | `1000` | Max range before auto-termination | +| `Acceleration` | `Vector3` | `(0,0,0)` | Constant acceleration applied each frame | +| `HighFidelityBehavior` | `number` | `1` | Default(1) / Automatic(2) / Always(3) | +| `HighFidelitySegmentSize` | `number` | `0.5` | Segment size for sub-stepping | +| `CosmeticBulletTemplate` | `BasePart?` | `nil` | Visual projectile part | +| `CosmeticBulletContainer` | `Instance?` | `nil` | Parent for non-cached bullets | +| `AutoIgnoreContainer` | `boolean` | `true` | Auto-adds container to filter list | +| `VisualizeCasts` | `boolean` | `false` | Debug visualization toggle | +| `VisualizeCastSettings` | `table` | (defaults) | Debug viz colors, sizes, lifetimes | +| `UserData` | `any` | `nil` | Arbitrary data accessible on the cast | + +### Event Configuration + +```lua +behavior.FastCastEventsConfig = { + UseLengthChanged = false, + UseHit = true, + UsePierced = true, + UseCastTerminating = true, + UseCanPierce = true, + UseCastFire = true +} + +behavior.FastCastEventsModuleConfig = { -- Parallel only + UseLengthChanged = false, + UseHit = true, + UsePierced = true, + UseCastTerminating = true, + UseCanPierce = true, + UseCastFire = true +} +``` + +--- + +## 3. ActiveCastData + +### 3.1 What is ActiveCast + +ActiveCast represents a projectile in flight. It's a pure data structure (AoS) exposed to users, while internally FastCast2 uses SoA for performance. + +### 3.2 Data Structure + +```lua +cast.ID: number -- Unique identifier +cast.CFrame: CFrame -- Current cosmetic bullet CFrame +cast.UserData: any -- From behavior.UserData +cast.CastVariant: { -- Cast type info + CastType: number, -- 1=Raycast, 2=Blockcast, 3=Spherecast + Size: Vector3?, -- Blockcast only + Radius: number? -- Spherecast only +} +``` + +### 3.3 StateInfo + +```lua +cast.StateInfo.TotalRuntime: number +cast.StateInfo.HighFidelityBehavior: number +cast.StateInfo.HighFidelitySegmentSize: number +cast.StateInfo.IsActivelyResimulating: boolean +cast.StateInfo.CancelHighResCast: boolean +cast.StateInfo.VisualizeCasts: boolean +cast.StateInfo.VisualizeCastSettings: VisualizeCastSettings +cast.StateInfo.FastCastEventsConfig: FastCastEventsConfig +cast.StateInfo.FastCastEventsModuleConfig: FastCastEventsModuleConfig -- Parallel only + +cast.StateInfo.Trajectory = { + StartTime: number, + EndTime: number, -- -1 if still active + Origin: Vector3, + InitialVelocity: Vector3, + Acceleration: Vector3 +} +``` + +### 3.4 RayInfo + +```lua +cast.RayInfo.Parameters: RaycastParams +cast.RayInfo.WorldRoot: WorldRoot +cast.RayInfo.MaxDistance: number +cast.RayInfo.CosmeticBulletObject: Instance? +cast.RayInfo.Size: Vector3 -- Blockcast only +cast.RayInfo.Radius: number -- Spherecast only +``` + +--- + +## 4. FastCastEventsModule + +Parallel-mode only. A `ModuleScript` that returns a `FastCastEvents` table for direct (non-BindableEvent) callbacks, providing better performance for high-frequency events like `LengthChanged`. + +```lua +-- In a ModuleScript (e.g., ReplicatedStorage.FastCastEventsModule) +local module = {} + +module.Hit = function(cast, result, velocity, bullet) + print("Hit:", result.Instance) +end + +module.CanPierce = function(cast, result, velocity, bullet) + return result.Instance:GetAttribute("CanPierce") == true +end + +module.LengthChanged = function(cast, lastPoint, rayDir, displacement) + -- Called every frame - more efficient than BindableEvent routing +end + +return module +``` + +Register it: +```lua +caster:SetFastCastEventsModule(pathToModule) +``` + +> **Note**: Not available in Serial mode — use direct event callbacks instead. + +--- + +## 5. High-Fidelity Behavior + +| Mode | Value | Description | +|------|-------|-------------| +| **Default** | `1` | Single cast per frame. Fastest, lowest accuracy. | +| **Automatic** | `2` | On hit, subdivides the frame's cast into sub-segments to find precise hit point. | +| **Always** | `3` | Always subdivides every frame. Most accurate, most expensive. | + +`HighFidelitySegmentSize` controls the segment size for sub-stepping (default 0.5 studs). + +--- + +## 6. Performance + +- **Serial Mode**: Single RunService connection with SoA (Structure of Arrays) for all active casts +- **Parallel Mode**: One RunService per Actor VM, each with its own SoA instance + +This eliminates per-cast RunService connections entirely, replacing them with dense array iteration — O(n) per frame regardless of mode. \ No newline at end of file diff --git a/docs/changelog.md b/docs/changelog.md index add4d873..df2f3efd 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -7,7 +7,40 @@ sidebar_position: 3 All notable changes to this project will be documented in this file. The format is based on Keep a Changelog (https://keepachangelog.com/en/1.0.0/) -and this project adheres to Semantic Versioning (https://semver.org/). + +--- + +## [0.1.0] — 2026-05-07 + +### Added +- **Serial Mode** (`FastCast.new()`) - Main thread projectile simulation, simpler API +- **Parallel Mode** (`FastCast.newParallel()`) - Worker VM based parallel simulation +- **Motor6D movement mode** - New movement method using Motor6D for better performance + - Pass `"Motor6D"` as the movement mode to `caster:Init()` +- **SerialSimulation** - Single RunService with SoA pattern for Serial casts +- **ParallelSimulation** - Per-Actor SoA pattern for Parallel casts +- **Motor6DCache** - Object pooling for Motor6D instances + +### Changed +- **API Restructure**: + - `.new()` now creates Serial caster (requires `Init(movementMode, useObjectCache, ...)`) + - `.newParallel()` creates Parallel caster (requires `Init(numWorkers, ...)`) + - Removed `FastCastParallel.new()` - use `.newParallel()` instead +- **ActiveCast** - Changed from OOP to pure data structure (AoS for users, SoA internally) +- **Trajectories** → **Trajectory** - Single object instead of array (saves memory) +- Removed **UpdateConnection** - No longer uses per-cast RunService connections +- Removed **xpcall/pcall** from hot path for performance +- Removed **FastCastEventsModule** from Serial mode (Parallel only) +- Removed `PauseCast`/`ResumeCast` - cast manipulation now uses `ModifyTransformation` pattern +- Renamed `SetBulkMoveEnabled` → `SetMovementMode(mode, enabled)` +- Removed `behavior.MovementMethod` - movement mode is set via `caster:Init()` or `caster:SetMovementMode()` + +### Fixed +- **HighFidelityBehavior = 2 bug** - Fixed subRayDir calculation using `delta` instead of `timeIncrement` + +### Performance +- Serial: 1 global RunService handling all casts with SoA arrays +- Parallel: 1 RunService per Actor with SoA arrays within each --- diff --git a/docs/cheatsheet.md b/docs/cheatsheet.md index c412a75d..ac663661 100644 --- a/docs/cheatsheet.md +++ b/docs/cheatsheet.md @@ -2,183 +2,192 @@ sidebar_position: 2 --- -# FastCast2 CheatSheet v0.0.9 +# FastCast2 CheatSheet -## Caster +## Caster — Serial Mode ```luau --// Construct & Init -local Caster = FastCast2.new() -- Construct a new Caster +local Caster = FastCast2.new() -- Construct a new Serial Caster Caster:Init( - numWorker: number, -- Number of worker VMs. Must be > 1. - newParent: Folder, -- Parent Folder for the FastCastVMs Folder. - newName: string, -- Name for the FastCastVMs Folder. - ContainerParent: Folder, -- Parent Folder for worker VM Containers. - VMname: string, -- Name given to each worker VM. - useBulkMoveTo: boolean, -- Enable BulkMoveTo for CosmeticBulletObjects. - FastCastEventsModule: ModuleScript, -- ModuleScript returning a FastCastEvents table. - useObjectCache: boolean, -- Enable ObjectCache for this Caster. - Template: BasePart | Model, -- Template object for ObjectCache (if enabled). - CacheHolder: Instance -- Parent Instance for cached objects (if enabled). + movementMode: "BulkMoveTo" | "Motor6D", -- Movement method for cosmetic bullets. + useObjectCache: boolean, -- Enable ObjectCache for this Caster. + Template: BasePart | Model?, -- Template for ObjectCache (if enabled). + CacheSize: number?, -- Number of objects to pre-allocate. + CacheHolder: Instance? -- Parent for cached objects. ) -- ⚠ Must be called before any Fire methods — nothing happens without Init! +--// Events (assign callbacks before Init) + +Caster.Hit = function(cast, result, velocity, cosmeticBullet) end +Caster.Pierced = function(cast, result, velocity, cosmeticBullet) end +Caster.LengthChanged = function(cast, lastPoint, rayDir, rayDisplacement) end +Caster.CastFire = function(cast, origin, direction, velocity, behavior) end +Caster.CastTerminating = function(cast) end +Caster.CanPierce = function(cast, result, velocity, cosmeticBullet) -> boolean end + --// Fire Methods -Caster:RaycastFire( - origin: Vector3, - direction: Vector3, - velocity: Vector3 | number, - BehaviorData: FastCastBehavior? -) → () -- Fire a raycast projectile. - -Caster:BlockcastFire( - origin: Vector3, - Size: Vector3, - direction: Vector3, - velocity: Vector3 | number, - BehaviorData: FastCastBehavior? -) → () -- Fire a blockcast projectile. - -Caster:SpherecastFire( - origin: Vector3, - Radius: number, - direction: Vector3, - velocity: Vector3 | number, - BehaviorData: FastCastBehavior? -) → () -- Fire a spherecast projectile. +Caster:RaycastFire(origin, direction, velocity, BehaviorData) +Caster:BlockcastFire(origin, Size, direction, velocity, BehaviorData) +Caster:SpherecastFire(origin, Radius, direction, velocity, BehaviorData) --// Configuration -Caster:SetFastCastEventsModule(moduleScript: ModuleScript) → () --- Set a new FastCastEventsModule for all future BaseCasts from this Caster. +Caster:SetMovementMode(mode: "BulkMoveTo" | "Motor6D") → () +Caster:SetObjectCacheEnabled(enabled, Template?, CacheSize?, CacheHolder?) → () + + +--// Lifecycle + +Caster:Destroy() → () +``` + +--- + +## Caster — Parallel Mode + +```luau +--// Construct & Init -Caster:SetBulkMoveEnabled(enabled: boolean) → () --- Toggle BulkMoveTo for cosmetic bullet CFrame updates. +local Caster = FastCast2.newParallel() -Caster:SetObjectCacheEnabled( - enabled: boolean, -- Toggle ObjectCache on/off. - Template: BasePart | Model, -- Projectile template to cache. - CacheSize: number, -- Number of objects to pre-allocate. - CacheHolder: Instance -- Where cached objects are stored. -) → () +Caster:Init( + numWorkers: number, -- Number of Actor VMs. Must be > 1. + newParent: Folder, -- Parent for the FastCastVMs Folder. + newName: string, -- Name for the FastCastVMs Folder. + ContainerParent: Folder, -- Parent for worker VM Containers. + VMContainerName: string, -- Name for VM Containers. + VMname: string, -- Name given to each worker VM. + movementMode: "BulkMoveTo" | "Motor6D", -- Movement method. + FastCastEventsModule: ModuleScript?,-- ModuleScript returning a FastCastEvents table. + useObjectCache: boolean, -- Enable ObjectCache for this Caster. + Template: BasePart | Model?, -- Template for ObjectCache (if enabled). + CacheSize: number?, -- Number of objects to pre-allocate. + CacheHolder: Instance? -- Parent for cached objects. +) +-- ⚠ Must be called before any Fire methods — nothing happens without Init! ---// Cast Manipulation (use Caster reference, not cast itself) +--// Fire Methods (same as Serial) -Caster:GetPositionCast(cast: vaildcast) → Vector3 -- Current position of the cast. -Caster:GetVelocityCast(cast: vaildcast) → Vector3 -- Current velocity of the cast. -Caster:GetAccelerationCast(cast: vaildcast) → Vector3 -- Current acceleration of the cast. +Caster:RaycastFire(origin, direction, velocity, BehaviorData) +Caster:BlockcastFire(origin, Size, direction, velocity, BehaviorData) +Caster:SpherecastFire(origin, Radius, direction, velocity, BehaviorData) -Caster:SetVelocityCast(cast: vaildcast, velocity: Vector3) → () -- Override velocity. -Caster:SetAccelerationCast(cast: vaildcast, acceleration: Vector3) → () -- Override acceleration. -Caster:AddPositionCast(cast: vaildcast, position: Vector3) → () -- Add to current position. -Caster:AddVelocityCast(cast: vaildcast, velocity: Vector3) → () -- Add to current velocity. -Caster:AddAccelerationCast(cast: vaildcast, acceleration: Vector3) → () -- Add to current acceleration. +--// Configuration --- ⚠ After any Set/Add call, sync changes or they won't take effect in the VM: -Caster:SyncChangesToCast(cast: vaildcast) → () -- Push pending state changes to the worker VM. +Caster:SetFastCastEventsModule(moduleScript: ModuleScript) → () +Caster:SetMovementMode(mode: "BulkMoveTo" | "Motor6D", enabled: boolean) → () +Caster:SetObjectCacheEnabled(enabled, Template?, CacheSize?, CacheHolder?) → () -Caster:PauseCast(cast: vaildcast) → () -- Pause simulation for a cast. -Caster:ResumeCast(cast: vaildcast) → () -- Resume a previously paused cast. -Caster:TerminateCast(cast: vaildcast) → () -- Forcefully terminate a cast early. +--// Cast Manipulation (use FastCast static methods) ---// Lifecycle +FastCast.GetPositionCast(cast) → Vector3 +FastCast.GetVelocityCast(cast) → Vector3 +FastCast.GetAccelerationCast(cast) → Vector3 -Caster:Destroy() → () -- Destroy the Caster and clean up all resources. +FastCast.SetVelocityCast(cast, velocity) → () +FastCast.SetAccelerationCast(cast, acceleration) → () +FastCast.SetPositionCast(cast, position) → () +FastCast.AddPositionCast(cast, position) → () +FastCast.AddVelocityCast(cast, velocity) → () +FastCast.AddAccelerationCast(cast, acceleration) → () ---// Fields +-- ⚠ After any Set/Add, sync changes to push state to the worker VM: +Caster:SyncChangesToCast(cast) → () --- Signals (assign a callback OR connect an RBXScriptConnection) -Caster.LengthChanged: (RBXScriptConnection | OnLengthChangedFunction)? --- Fired every simulation step with: cast, segmentOrigin, segmentDirection, length, segmentVelocity, cosmeticBullet +FastCast.TerminateCast(cast) → () -Caster.Hit: (RBXScriptConnection | OnHitFunction)? --- Fired when the cast hits something (non-piercing): cast, raycastResult, segmentVelocity, cosmeticBullet -Caster.Pierced: (RBXScriptConnection | OnPierceFunction)? --- Fired when the cast pierces through something: cast, raycastResult, segmentVelocity, cosmeticBullet +--// Lifecycle -Caster.CastFire: (RBXScriptConnection | OnCastFireFunction)? --- Fired when a cast is initially launched: cast, origin, direction, velocity, behavior +Caster:Destroy() → () +``` -Caster.CastTerminating: (RBXScriptConnection | OnCastTerminatingFunction)? --- Fired just before a cast is destroyed: cast +--- --- State -Caster.AlreadyInit: boolean -- True after Init() has been called. -Caster.ObjectCacheEnabled: boolean -- Whether ObjectCache is currently active. -Caster.BulkMoveEnabled: boolean -- Whether BulkMoveTo is currently active. +## FastCastBehavior --- References -Caster.WorldRoot: WorldRoot -- The WorldRoot this Caster simulates in. -Caster.FastCastEventsModule: ModuleScript -- The currently assigned FastCastEvents module. -Caster.ObjectCache: ObjectCache -- The ObjectCache instance (if enabled). -Caster.Dispatcher: Dispatcher -- Internal worker VM dispatcher. +```luau +local behavior = FastCast2.newBehavior() + +behavior.RaycastParams = RaycastParams.new() +behavior.MaxDistance = 1000 +behavior.Acceleration = Vector3.new(0, -196.2, 0) +behavior.HighFidelityBehavior = 1 -- Default | Automatic(2) | Always(3) +behavior.HighFidelitySegmentSize = 0.5 +behavior.CosmeticBulletTemplate = somePart -- Visual projectile +behavior.CosmeticBulletContainer = workspace -- Parent for non-cached bullets +behavior.AutoIgnoreContainer = true +behavior.VisualizeCasts = false +behavior.VisualizeCastSettings = { ... } -- Debug viz colors/sizes +behavior.UserData = {} -- Arbitrary data accessible on the cast + +behavior.FastCastEventsConfig = { + UseLengthChanged = false, + UseHit = true, + UsePierced = true, + UseCastTerminating = true, + UseCanPierce = true, + UseCastFire = true +} + +behavior.FastCastEventsModuleConfig = { + UseLengthChanged = false, + UseHit = true, + UsePierced = true, + UseCastTerminating = true, + UseCanPierce = true, + UseCastFire = true +} ``` -```lua --- ActiveCastData fields --- All three cast variants (Raycast, Blockcast, Spherecast) share the same structure. - -cast.ID: number -- Unique identifier for this ActiveCast instance. -cast.Type: "Raycast" | "Blockcast" | "Spherecast" -- The cast variant type. -cast.CFrame: CFrame -- Current CFrame of the cosmetic bullet object. -cast.UserData: { [any]: any } -- Free-use table for storing custom data on the cast. - -cast.Caster: BaseCastData -- Reference to the parent BaseCastData (internal caster bindings). -cast.RayInfo: CastRayInfo -- Ray/cast geometry info (params, world root, max distance, cosmetic object, pierce module). -cast.StateInfo: CastStateInfo -- Runtime state of the cast (see below). - ---// CastStateInfo (cast.StateInfo) - -cast.StateInfo.UpdateConnection: RBXScriptSignal -- The heartbeat/stepped connection driving this cast. -cast.StateInfo.Paused: boolean -- Whether the cast is currently paused. -cast.StateInfo.TotalRuntime: number -- Total elapsed simulation time (seconds). -cast.StateInfo.DistanceCovered: number -- Total distance traveled so far (studs). -cast.StateInfo.IsActivelySimulatingPierce: boolean -- True while the cast is processing a pierce check. -cast.StateInfo.IsActivelyResimulating: boolean -- True while the cast is doing a high-fidelity resimulation pass. -cast.StateInfo.CancelHighResCast: boolean -- Set to true to abort the current high-res cast. -cast.StateInfo.HighFidelityBehavior: number -- Current high-fidelity behavior mode. -cast.StateInfo.HighFidelitySegmentSize: number -- Segment size used in high-fidelity mode. -cast.StateInfo.VisualizeCasts: boolean -- Whether debug visualization is active for this cast. -cast.StateInfo.Trajectories: { [number]: CastTrajectory } -- List of trajectory segments for this cast. - -cast.StateInfo.VisualizeCastSettings: VisualizeCastSettings -- Debug visualization config. -cast.StateInfo.FastCastEventsConfig: FastCastEventsConfig -- Which events are enabled (non-module callbacks). -cast.StateInfo.FastCastEventsModuleConfig: FastCastEventsModuleConfig -- Which module events are enabled. - ---// CastTrajectory (entry in cast.StateInfo.Trajectories) - -trajectory.StartTime: number -- Simulation time when this trajectory segment began. -trajectory.EndTime: number -- Simulation time when this segment ended (0 if still active). -trajectory.Origin: Vector3 -- Origin point of this segment. -trajectory.InitialVelocity: Vector3 -- Velocity at the start of this segment. -trajectory.Acceleration: Vector3 -- Acceleration applied throughout this segment. - ---// CastRayInfo (cast.RayInfo) - -cast.RayInfo.Parameters: RaycastParams -- RaycastParams used for this cast. -cast.RayInfo.WorldRoot: WorldRoot -- The WorldRoot the cast is simulating in. -cast.RayInfo.MaxDistance: number -- Maximum travel distance before the cast terminates. -cast.RayInfo.CosmeticBulletObject: Instance? -- The cosmetic bullet object attached to this cast (if any). -cast.RayInfo.CanPierceModule: ModuleScript? -- Optional pierce-decision module (legacy / manual pierce setup). - -cast.RayInfo.Size: Vector3 -- Blockcast addon RayInfo -cast.RayInfo.Radius: number -- Spherecast addon RayInfo - ---// BaseCastData (cast.Caster) - -cast.Caster.Output: BindableEvent -- Internal event used to relay cast signals back to the Caster. -cast.Caster.ActiveCastCleaner: BindableEvent -- Fired when an ActiveCast is cleaned up. -cast.Caster.SyncChange: BindableEvent -- Used to sync manual state changes (velocity, position, etc.) back to the VM. -cast.Caster.ObjectCache: BindableFunction? -- Reference to the ObjectCache BindableFunction (if ObjectCache is enabled). -cast.Caster.CacheHolder: any? -- The Instance holding cached objects (if ObjectCache is enabled). +--- + +## ActiveCastData + +```luau +cast.ID: number -- Unique cast identifier +cast.Type: number -- 1=Raycast, 2=Blockcast, 3=Spherecast +cast.CFrame: CFrame -- Current cosmetic bullet CFrame +cast.UserData: any -- Custom data from behavior.UserData +cast.CastVariant: table -- { CastType, Size?|Radius? } + +--// cast.StateInfo + +cast.StateInfo.TotalRuntime: number +cast.StateInfo.HighFidelityBehavior: number +cast.StateInfo.HighFidelitySegmentSize: number +cast.StateInfo.IsActivelyResimulating: boolean +cast.StateInfo.CancelHighResCast: boolean +cast.StateInfo.FastCastEventsConfig: FastCastEventsConfig +cast.StateInfo.FastCastEventsModuleConfig: FastCastEventsModuleConfig -- Parallel only +cast.StateInfo.VisualizeCasts: boolean +cast.StateInfo.VisualizeCastSettings: VisualizeCastSettings + +cast.StateInfo.Trajectory = { + StartTime: number, + EndTime: number, -- -1 if still active + Origin: Vector3, + InitialVelocity: Vector3, + Acceleration: Vector3 +} + +--// cast.RayInfo + +cast.RayInfo.Parameters: RaycastParams +cast.RayInfo.WorldRoot: WorldRoot +cast.RayInfo.MaxDistance: number +cast.RayInfo.CosmeticBulletObject: Instance? +cast.RayInfo.Size: Vector3 -- Blockcast only +cast.RayInfo.Radius: number -- Spherecast only ``` diff --git a/docs/intro.md b/docs/intro.md index 5a57e9c7..9d152d0f 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -4,7 +4,7 @@ sidebar_position: 1 # Introduction -**FastCast2** It's a **Roblox projectile library** powered by [VMsDispatcher](https://github.com/weenachuangkud/VMsDispatcher) designed to simulate **thousands** of projectiles without relying on physics replication. +**FastCast2** is a **Roblox projectile library** powered by [VMsDispatcher](https://github.com/weenachuangkud/VMsDispatcher) designed to simulate **thousands** of projectiles without relying on physics replication. Because FastCast is no longer actively maintained by [EtiTheSpirit](https://github.com/EtiTheSpirit), this repository continues the project with updates and adaptations. diff --git a/package.json b/package.json index 28f127f9..775ed406 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,8 @@ { "dependencies": { "moonwave": "^1.3.0" + }, + "devDependencies": { + "selene": "^1.1.1" } } diff --git a/rokit.toml b/rokit.toml index 1ecbea05..f9a0f232 100644 --- a/rokit.toml +++ b/rokit.toml @@ -6,3 +6,5 @@ [tools] rojo = "rojo-rbx/rojo@7.6.1" wally = "UpliftGames/wally@0.3.2" +luau-lsp = "JohnnyMorganz/luau-lsp@1.68.0" +selene = "Kampfkarren/selene@0.31.0" diff --git a/skills/architecture.md b/skills/architecture.md new file mode 100644 index 00000000..c12cf390 --- /dev/null +++ b/skills/architecture.md @@ -0,0 +1,218 @@ +# FastCast2 Architecture + +## Overview + +FastCast2 is a Roblox projectile library in Luau that simulates projectile physics using **workspace raycasting** (not Roblox physics). It supports **raycast, blockcast, and spherecast** casting types, each following the same projectile simulation lifecycle. + +The library provides two execution modes: + +| Mode | Execution | Threading | Caster Factory | Simulation | BaseCast | +|------|-----------|-----------|----------------|------------|----------| +| **Serial** (`FastCast.new()`) | Main thread | Single RunService connection | `FastCastSerial` | `SerialSimulation` | `BaseCastSerial` | +| **Parallel** (`FastCast.newParallel()`) | Actor VMs | One VM per worker, round-robin dispatch | `FastCastParallel` | `ParallelSimulation` | `BaseCastParallel` | + +--- + +## Module Layout (`src/`) + +| File | Role | +|------|------| +| `init.luau` | Entry point. Exports `FastCast` static methods + `FastCastSerial` / `FastCastParallel` caster constructors. | +| `BaseCastSerial.luau` | Serial-mode caster handler. Owns `ObjectCache`, `Motor6DCache`, `SerialSimulation`. Routes `Raycast/Blockcast/Spherecast` calls to simulation. | +| `BaseCastParallel.luau` | Parallel-mode caster handler. Same responsibility but lives inside each Actor VM (module-scoped state). | +| `SerialSimulation.luau` | SoA physics engine (serial). Connected to RunService on main thread. | +| `ParallelSimulation.luau` | SoA physics engine (parallel). Connected via `ConnectParallel` inside Actor VM. | +| `ActiveCast.luau` | Cast data factory. Creates the cast data table used by both modes. | +| `ObjectCache.luau` | Cosmetic bullet part pooling (bulk-move based). | +| `Motor6DCache.luau` | Motor6D pooling for Transform movement mode. | +| `TypeDefinitions.luau` | All Luau type exports. | +| `FastCastEnums.luau` | Enums: `HighFidelityBehavior` (Default/Automatic/Always), `CastType` (Raycast/Blockcast/Spherecast). | +| `Config.luau` | Debug logging toggles. | +| `DefaultConfigs.luau` | Default `FastCastBehavior` values. | +| `FastCastVMs/init.luau` | VM Dispatcher — creates/manages Actor VMs, round-robins fire requests. | +| `FastCastVMs/ClientVM.client.luau` | Client-side Actor script running inside each VM. | +| `FastCastVMs/ServerVM.server.luau` | Server-side Actor script running inside each VM. | + +--- + +## Execution Flow + +### Initialization + +``` +FastCast.new() or FastCast.newParallel() + └─> Returns FastCastSerial / FastCastParallel metatable + +caster:Init(...) + └─> Serial: creates BaseCastSerial → SerialSimulation → Start() (RunService.Heartbeat/PreSimulation) + └─> Parallel: creates VM Dispatcher → spawns Actor VMs → each VM creates BaseCastParallel → ParallelSimulation → Start() (ConnectParallel) +``` + +### Firing a Cast + +``` +caster:RaycastFire(origin, direction, velocity, behavior) + OR +caster:BlockcastFire(origin, size, direction, velocity, behavior) + OR +caster:SpherecastFire(origin, radius, direction, velocity, behavior) + └─> Serial: BaseCastSerial creates ActiveCast data → SerialSimulation.Register() → fires CastFire event + └─> Parallel: Dispatcher:Dispatch() → round-robins to next Actor VM → BaseCastParallel creates ActiveCast data → ParallelSimulation.Register() → fires CastFire event +``` + +### Per-Frame Simulation + +Each frame the simulation engine: + +1. **Iterates all registered cast IDs** stored in a dense `casts_ID` array (fast iteration). +2. **Computes position** at current runtime using kinematic equation: `P(t) = origin + velocity*t + 0.5*acceleration*t²` +3. **Performs a workspace cast** (Raycast/Blockcast/Spherecast) from the last position toward the current position. +4. **Handles hits** — if a part is hit: + - Checks `CanPierce` callback + - If not piercing: queues `Hit` + `CastTerminating` events (with optional High-Fidelity sub-stepping) + - If piercing: queues `Pierced` event, continues simulation +5. **Checks MaxDistance** — terminates if exceeded. +6. **Queues events** (`LengthChanged`, `Hit`, `Pierced`, `CastTerminating`) for deferred firing. +7. **Updates cosmetic bullet position** via `BulkMoveTo` or `Motor6D.Transform`. +8. **Fires queued events** sorted by cast ID. + +--- + +## SoA (Structure of Arrays) Pattern + +Both `SerialSimulation` and `ParallelSimulation` use the SoA pattern for cache-efficient per-frame iteration: + +```lua +local casts_TotalRunTime = {} :: { [number]: number } +local casts_Trajectory = {} :: { [number]: CastTrajectory } +local casts_RayInfo = {} :: { [number]: CastRayInfo } +-- ... ~20 SoA arrays total +local casts_ID = {} :: { number } -- dense active-ID list +local casts_ID_Index = {} :: { [number]: number } -- reverse lookup +``` + +- **Registration**: Cast data is split across arrays by `cast.ID`. +- **Iteration**: The dense `casts_ID` list is iterated each frame with a simple numeric `for` loop. +- **Unregistration**: O(1) removal via swap-and-pop: the last ID replaces the removed ID's slot. + +--- + +## High-Fidelity Sub-Stepping + +Three modes controlled by `HighFidelityBehavior`: + +| Mode | Behavior | +|------|----------| +| **Default (1)** | Single cast per frame. Fastest, lowest accuracy. | +| **Automatic (2)** | Upon hit, subdivides the frame's displacement into `displacement / HighFidelitySegmentSize` segments and recasts each. Finds the precise hit point. | +| **Always (3)** | Always subdivides every frame's cast. Most accurate, most expensive. | + +--- + +## Cast Customization + +### FastCastBehavior + +Configured via `FastCast.newBehavior()` then populated: + +| Field | Type | Purpose | +|-------|------|---------| +| `RaycastParams` | `RaycastParams?` | Filter rules | +| `MaxDistance` | `number` | Max range before auto-termination | +| `Acceleration` | `Vector3` | Constant acceleration applied each frame | +| `HighFidelityBehavior` | `number` | Default / Automatic / Always | +| `HighFidelitySegmentSize` | `number` | Segment size for sub-stepping | +| `CosmeticBulletTemplate` | `BasePart?` | Visual bullet part | +| `CosmeticBulletContainer` | `Instance?` | Parent for non-cached bullet instances | +| `AutoIgnoreContainer` | `boolean` | Auto-adds bullet container to filter list | +| `FastCastEventsConfig` | table | Enables/disables built-in event callbacks | +| `FastCastEventsModuleConfig` | table | Enables/disables module-script event callbacks | +| `VisualizeCasts` | `boolean` | Debug visualization | +| `VisualizeCastSettings` | table | Visualization colors/sizes | +| `UserData` | `any` | Arbitrary user data attached to the cast | + +--- + +## Event System + +Two event channels exist in **parallel mode**; serial mode uses direct callbacks: + +1. **FastCastEvents** (built-in) — configured via `FastCastEventsConfig`: + - `CastFire`, `Hit`, `Pierced`, `LengthChanged`, `CastTerminating`, `CanPierce` + +2. **FastCastEventsModule** (user-supplied ModuleScript) — configured via `FastCastEventsModuleConfig`: + - Same event names, resolved via `require()`. Only available in parallel mode. + +### Parallel Event Routing + +``` +BaseCastParallel + ├─> Output:BindableEvent:Fire("Hit", cast, ...) → Dispatcher callback → user event handler + └─> CastFireFunc functions (from module) → direct call +``` + +### Serial Event Routing + +``` +BaseCastSerial + └─> user-provided events table → direct callbacks during FireQueuedEvents +``` + +--- + +## Caching Systems + +### ObjectCache (`ObjectCache.luau`) + +- Pools cosmetic bullet parts/models for reuse. +- Uses `BulkMoveTo` to move parts to/from a far-away CFrame. +- Pre-allocates on init, auto-expands by 50 when exhausted. + +### Motor6DCache (`Motor6DCache.luau`) + +- Pools `Motor6D` instances for Transform movement mode. +- Connects cosmetic parts to an invisible anchored anchor part via Motor6Ds. +- Movement is applied by setting `Motor6D.Transform` each frame. + +--- + +## Parallel Architecture (`FastCastVMs/`) + +### VM Dispatcher (`FastCastVMs/init.luau`) + +- Creates a template `Actor` with a `ClientVM` or `ServerVM` script inside. +- Clones the actor `N` times into a container folder. +- `Dispatch()` sends a message to the next actor in round-robin order. +- `DispatchAll()` sends to every actor (for settings changes). + +### Actor Scripts (`ClientVM.client.luau`, `ServerVM.server.luau`) + +- Receive `"Init"` message → create `BaseCastParallel`. +- Receive `"Raycast"` / `"Blockcast"` / `"Spherecast"` messages → call corresponding `BaseCastParallel` method. +- Receive `"BindObjectCache"` / `"SetMovementMode"` / `"SetFastCastEventsModule"` → update shared state. +- Receive `"Destroy"` → cleanup. + +### Data Flow + +``` +Script requiring FastCast2 + ├─> init.luau (entry) + │ ├─> FastCastSerial (metatable for serial casters) + │ └─> FastCastParallel (metatable for parallel casters) + │ + ├─> FastCastVMs/init.luau (Dispatcher) + │ └─> Spawns N Actor VMs + │ ├─> ClientVM / ServerVM (Actor script) + │ │ └─> BaseCastParallel (inside VM) + │ │ ├─> ParallelSimulation (SoA engine) + │ │ ├─> ActiveCast (cast data factory) + │ │ ├─> ObjectCache (bullet pooling) + │ │ └─> Motor6DCache (Motor6D pooling) + │ └─> ... repeat for N workers + │ + └─> BaseCastSerial (used for serial casters) + ├─> SerialSimulation (SoA engine) + ├─> ActiveCast (cast data factory) + ├─> ObjectCache (bullet pooling) + └─> Motor6DCache (Motor6D pooling) +``` diff --git a/sourcemap.json b/sourcemap.json index 32e8fbae..535f6d34 100644 --- a/sourcemap.json +++ b/sourcemap.json @@ -1 +1 @@ -{"name":"FastCast2","className":"DataModel","filePaths":["default.project.json"],"children":[{"name":"ReplicatedStorage","className":"ReplicatedStorage","children":[{"name":"FastCast2","className":"ModuleScript","filePaths":["src/FastCast2/init.luau"],"children":[{"name":"ActiveCast","className":"ModuleScript","filePaths":["src/FastCast2/ActiveCast.luau"]},{"name":"BaseCast","className":"ModuleScript","filePaths":["src/FastCast2/BaseCast.luau"]},{"name":"Configs","className":"ModuleScript","filePaths":["src/FastCast2/Configs.luau"]},{"name":"DefaultConfigs","className":"ModuleScript","filePaths":["src/FastCast2/DefaultConfigs.luau"]},{"name":"FastCastEnums","className":"ModuleScript","filePaths":["src/FastCast2/FastCastEnums.luau"]},{"name":"FastCastVMs","className":"ModuleScript","filePaths":["src/FastCast2/FastCastVMs/init.luau"],"children":[{"name":"ClientVM","className":"LocalScript","filePaths":["src/FastCast2/FastCastVMs/ClientVM.client.luau","src/FastCast2/FastCastVMs/ClientVM.meta.json"]},{"name":"ServerVM","className":"Script","filePaths":["src/FastCast2/FastCastVMs/ServerVM.server.luau","src/FastCast2/FastCastVMs/ServerVM.meta.json"]}]},{"name":"ObjectCache","className":"ModuleScript","filePaths":["src/FastCast2/ObjectCache.luau"]},{"name":"Signal","className":"ModuleScript","filePaths":["src/FastCast2/Signal.luau"]},{"name":"TypeDefinitions","className":"ModuleScript","filePaths":["src/FastCast2/TypeDefinitions.luau"]}]}]}]} \ No newline at end of file +{"name":"FastCast2","className":"DataModel","filePaths":["default.project.json"],"children":[{"name":"ReplicatedStorage","className":"ReplicatedStorage","children":[{"name":"FastCast2","className":"ModuleScript","filePaths":["src/init.luau"],"children":[{"name":"ActiveCast","className":"ModuleScript","filePaths":["src/ActiveCast.luau"]},{"name":"BaseCastParallel","className":"ModuleScript","filePaths":["src/BaseCastParallel.luau"]},{"name":"BaseCastSerial","className":"ModuleScript","filePaths":["src/BaseCastSerial.luau"]},{"name":"Config","className":"ModuleScript","filePaths":["src/Config.luau"]},{"name":"DefaultConfigs","className":"ModuleScript","filePaths":["src/DefaultConfigs.luau"]},{"name":"FastCastEnums","className":"ModuleScript","filePaths":["src/FastCastEnums.luau"]},{"name":"FastCastVMs","className":"ModuleScript","filePaths":["src/FastCastVMs/init.luau"],"children":[{"name":"ClientVM","className":"LocalScript","filePaths":["src/FastCastVMs/ClientVM.client.luau","src/FastCastVMs/ClientVM.meta.json"]},{"name":"ServerVM","className":"Script","filePaths":["src/FastCastVMs/ServerVM.server.luau","src/FastCastVMs/ServerVM.meta.json"]}]},{"name":"Motor6DCache","className":"ModuleScript","filePaths":["src/Motor6DCache.luau"]},{"name":"ObjectCache","className":"ModuleScript","filePaths":["src/ObjectCache.luau"]},{"name":"ParallelSimulation","className":"ModuleScript","filePaths":["src/ParallelSimulation.luau"]},{"name":"SerialSimulation","className":"ModuleScript","filePaths":["src/SerialSimulation.luau"]},{"name":"TypeDefinitions","className":"ModuleScript","filePaths":["src/TypeDefinitions.luau"]}]}]}]} \ No newline at end of file diff --git a/src/ActiveCast.luau b/src/ActiveCast.luau new file mode 100644 index 00000000..10997f9d --- /dev/null +++ b/src/ActiveCast.luau @@ -0,0 +1,148 @@ +--[[ + - Author : Mawin CK + - Date : 2025 + + + ActiveCastSerial - Serial mode with single RunService, SoA pattern, queue technique + Similar to SwiftCast implementation +]] + +local FastCastModule = script.Parent +local TypeDef = require(FastCastModule:WaitForChild("TypeDefinitions")) +local FastCastEnums = require(FastCastModule:WaitForChild("FastCastEnums")) + +local DEFAULT_MAX_DISTANCE = 1000 + +local EnumCastTypes = FastCastEnums.CastType + +type CastVariant = { CastType: number, Size: Vector3?, Radius: number? } + +type BlockcastVariant = { CastType: number, Size: Vector3} +type SpherecastVariant = { CastType: number, Radius: number } +type CastVariants = BlockcastVariant | SpherecastVariant + + +--[[local CastVariantTypes = { + [EnumCastTypes.Raycast] = "Raycast", + [EnumCastTypes.Blockcast] = "Blockcast", + [EnumCastTypes.Spherecast] = "Spherecast" +}]] + +local ActiveCast = {} + +local function CloneCastParams(params: RaycastParams): RaycastParams + local clone: RaycastParams = RaycastParams.new() + clone.CollisionGroup = params.CollisionGroup + clone.FilterType = params.FilterType + clone.FilterDescendantsInstances = {table.unpack(params.FilterDescendantsInstances)} + clone.IgnoreWater = params.IgnoreWater + clone.RespectCanCollide = params.RespectCanCollide + return clone +end + +function ActiveCast.createCastData( + BaseCast: any, + activeCastID: number, + origin: Vector3, + direction: Vector3, + velocity: Vector3 | number, + behavior: TypeDef.FastCastBehavior, + eventModule: TypeDef.FastCastEventsModule?, + variant: CastVariants, + ObjectCacheRef: any, + _parallel: boolean? +): any + local cast = { + Caster = BaseCast, + StateInfo = { + TotalRuntime = 0, + DistanceCovered = 0, + HighFidelitySegmentSize = behavior.HighFidelitySegmentSize, + HighFidelityBehavior = behavior.HighFidelityBehavior, + IsActivelyResimulating = false, + CancelHighResCast = false, + Trajectory = { + StartTime = 0, + EndTime = -1, + Origin = origin, + InitialVelocity = if typeof(velocity) == "number" then direction * velocity else velocity, + Acceleration = behavior.Acceleration, + }, + + FastCastEventsConfig = { + UseLengthChanged = behavior.FastCastEventsConfig.UseLengthChanged, + UseHit = behavior.FastCastEventsConfig.UseHit, + UsePierced = behavior.FastCastEventsConfig.UsePierced, + UseCastTerminating = behavior.FastCastEventsConfig.UseCastTerminating, + UseCanPierce = behavior.FastCastEventsConfig.UseCanPierce + }, + + VisualizeCasts = behavior.VisualizeCasts, + VisualizeCastSettings = behavior.VisualizeCastSettings, + }, + + RayInfo = { + Parameters = behavior.RaycastParams and CloneCastParams(behavior.RaycastParams) or RaycastParams.new(), + WorldRoot = workspace, + MaxDistance = behavior.MaxDistance or DEFAULT_MAX_DISTANCE, + CosmeticBulletObject = behavior.CosmeticBulletTemplate, + FastCastEventsModule = eventModule + }, + + Type = variant.CastType, + CastVariant = variant, + CFrame = CFrame.new(origin), + ID = activeCastID + } + + if _parallel then + cast.StateInfo.FastCastEventsModuleConfig = { + UseLengthChanged = behavior.FastCastEventsModuleConfig.UseLengthChanged, + UseHit = behavior.FastCastEventsModuleConfig.UseHit, + UsePierced = behavior.FastCastEventsModuleConfig.UsePierced, + UseCastTerminating = behavior.FastCastEventsModuleConfig.UseCastTerminating, + UseCanPierce = behavior.FastCastEventsModuleConfig.UseCanPierce, + } + end + + if variant.CastType == EnumCastTypes.Blockcast then + cast.RayInfo.Size = (variant :: BlockcastVariant).Size + elseif variant.CastType == EnumCastTypes.Spherecast then + cast.RayInfo.Radius = (variant :: SpherecastVariant).Radius + end + + if behavior.UserData then + cast.UserData = behavior.UserData + end + + local targetContainer: Instance? + if ObjectCacheRef then + cast.RayInfo.CosmeticBulletObject = ObjectCacheRef:GetPart(if direction ~= Vector3.new() then CFrame.new(origin, origin + direction) else CFrame.new(origin)) + targetContainer = cast.Caster.CacheHolder + else + if cast.RayInfo.CosmeticBulletObject ~= nil then + local basePart = cast.RayInfo.CosmeticBulletObject + basePart = basePart:Clone() + basePart.CFrame = if direction ~= Vector3.new() then CFrame.new(origin, origin + direction) else CFrame.new(origin) + basePart.Parent = behavior.CosmeticBulletContainer + + cast.RayInfo.CosmeticBulletObject = basePart + end + + if behavior.CosmeticBulletContainer then + targetContainer = behavior.CosmeticBulletContainer + end + end + + if behavior.AutoIgnoreContainer == true and targetContainer ~= nil then + local ignoreList = cast.RayInfo.Parameters.FilterDescendantsInstances + if not table.find(ignoreList, targetContainer) then + table.insert(ignoreList, targetContainer) + cast.RayInfo.Parameters.FilterDescendantsInstances = ignoreList + end + end + + return cast +end + +return ActiveCast \ No newline at end of file diff --git a/src/BaseCastParallel.luau b/src/BaseCastParallel.luau new file mode 100644 index 00000000..83db63f6 --- /dev/null +++ b/src/BaseCastParallel.luau @@ -0,0 +1,410 @@ +--[[ + - Author : Mawin CK + - Date : 2025 + +]] + +local FastCast2 = script.Parent + +local FastCastEnums = require(FastCast2:WaitForChild("FastCastEnums")) +local ActiveCast = require(FastCast2:WaitForChild("ActiveCast")) +local TypeDef = require(FastCast2:WaitForChild("TypeDefinitions")) +local ParallelSimulation = require(FastCast2:WaitForChild("ParallelSimulation")) +local ObjectCache = require(FastCast2:WaitForChild("ObjectCache")) +local Motor6DCache = require(FastCast2:WaitForChild("Motor6DCache")) +local FastCastEventsModule: ModuleScript? = nil + +local EnumCastTypes = FastCastEnums.CastType + +local BaseCast = {} +BaseCast.__index = BaseCast +BaseCast.__type = "BaseCast" + +local DEFAULT_CACHE_SIZE = 500 +local DEFAULT_CACHE_HOLDER = workspace + +local Actor = nil +local Output = nil +local ActiveCastCleaner: BindableEvent = nil +local ObjectCacheInstance: any = nil +local Motor6DCacheInstance: any = nil +local NextProjectileID = 0 +local SyncChanges: BindableEvent = nil +local SyncChangesConnection: RBXScriptConnection? = nil +local CastFireFunc = nil +local CurrentMovementMode: "BulkMoveTo" | "Motor6D" = "BulkMoveTo" + +local function SendCastFire( + cast: TypeDef.ActiveCastData, + origin: Vector3, + direction: Vector3, + velocity: Vector3 | number, + behavior: TypeDef.FastCastBehavior +) + (cast.Caster :: any).Output:Fire("CastFire", cast, origin, direction, velocity, behavior) +end + +local function TerminateCast(cast: any, castTerminatingFunction: TypeDef.OnCastTerminatingFunction?) + local FastCastEventsConfig = cast.StateInfo.FastCastEventsConfig + if FastCastEventsConfig and FastCastEventsConfig.UseCastTerminating then + cast.Caster.Output:Fire("CastTerminating", cast) + end + + if castTerminatingFunction then + castTerminatingFunction((cast :: any)) + end + + cast.Caster.ActiveCastCleaner:Fire(cast.ID) + + for key, _ in (cast :: any) do + cast[key] = nil + end +end + +function BaseCast.Init(BindableOutput: BindableEvent, Data: any) + local self = setmetatable({}, BaseCast) + Actor = BindableOutput.Parent + self.Actives = {} + Output = BindableOutput + + local BindableCleaner = Instance.new("BindableEvent") + BindableCleaner.Name = "ActiveCastDestroyer" + BindableCleaner.Parent = Actor + + if Data.useObjectCache then + local objectCacheArgs = Data.objectCacheArgs or {} + if not objectCacheArgs.CacheSize then + objectCacheArgs.CacheSize = DEFAULT_CACHE_SIZE + end + + if not objectCacheArgs.CacheHolder then + objectCacheArgs.CacheHolder = DEFAULT_CACHE_HOLDER + end + + ObjectCacheInstance = ObjectCache.new(objectCacheArgs.Template, objectCacheArgs.CacheSize, objectCacheArgs.CacheHolder) :: any + end + + CurrentMovementMode = Data.movementMode or "BulkMoveTo" + if CurrentMovementMode == "Motor6D" then + Motor6DCacheInstance = Motor6DCache.new() + end + + ActiveCastCleaner = BindableCleaner + + ActiveCastCleaner.Event:Connect(function(activeCastID: number) + if self.Actives[activeCastID] then + local cast = self.Actives[activeCastID] + if cast.RayInfo and cast.RayInfo.CosmeticBulletObject then + if ObjectCacheInstance then + ObjectCacheInstance:ReturnPart(cast.RayInfo.CosmeticBulletObject) + else + cast.RayInfo.CosmeticBulletObject:Destroy() + cast.RayInfo.CosmeticBulletObject = nil + end + end + self.Actives[activeCastID] = nil + ParallelSimulation.Unregister(activeCastID) + Actor:SetAttribute("Tasks", Actor:GetAttribute("Tasks") - 1) + end + end) + + SyncChanges = Instance.new("BindableEvent") + SyncChanges.Name = "SyncChanges" + SyncChanges.Parent = Actor + + SyncChangesConnection = SyncChanges.Event:Connect(function(cast: TypeDef.ActiveCastData) + local ID = cast.ID + local TargetCast = self.Actives[ID] + + if TargetCast then + for i, v in cast do + if i == "StateInfo" and type(v) == "table" and type(TargetCast[i]) == "table" then + for k, v2 in v do + if k == "Trajectory" and type(v2) == "table" and type(TargetCast[i][k]) == "table" then + for tk, tv in v2 do + TargetCast[i][k][tk] = tv + end + else + TargetCast[i][k] = v2 + end + end + else + TargetCast[i] = v + end + end + end + end) + + ParallelSimulation.Init(self) + + ParallelSimulation.SetMovementModeEnabled(true, CurrentMovementMode) + + ParallelSimulation.Start() + + return self +end + +--[=[ + +@method Raycast +@within BaseCast + +@param Origin Vector3 -- The origin of the raycast. +@param Direction Vector3 -- The direction of the raycast. +@param Velocity Vector3 | number -- The velocity of the raycast. +@param Behavior FastCastBehavior -- The behavior data for the raycast. +@param GUID string -- The unique identifier for the raycast. + +Create a raycast. + +]=] +function BaseCast:Raycast( + Origin: Vector3, + Direction: Vector3, + Velocity: Vector3 | number, + Behavior: TypeDef.FastCastBehavior +) + Actor:SetAttribute("Tasks", Actor:GetAttribute("Tasks") + 1) + NextProjectileID += 1 + + local cast = ActiveCast.createCastData({ + Output = Output, + ActiveCastCleaner = ActiveCastCleaner, + SyncChange = SyncChanges + }, NextProjectileID, Origin, Direction, Velocity, Behavior, FastCastEventsModule, { + CastType = EnumCastTypes.Raycast + } :: any, ObjectCacheInstance, true) + + ParallelSimulation.Register(cast) + self.Actives[cast.ID] = cast + + if Behavior.FastCastEventsConfig.UseCastFire then + SendCastFire(cast, Origin, Direction, Velocity, Behavior) + end + if Behavior.FastCastEventsModuleConfig.UseCastFire and CastFireFunc then + CastFireFunc(cast, Origin, Direction, Velocity, Behavior) + end +end + +--[=[ + +@method SetFastCastEventsModule +@within BaseCast + +@param moduleScript ModuleScript -- The FastCastEventsModule to set. + +]=] +function BaseCast:SetFastCastEventsModule(moduleScript: ModuleScript) + FastCastEventsModule = moduleScript + if moduleScript and typeof(moduleScript) == "Instance" and moduleScript:IsA("ModuleScript") then + CastFireFunc = require(moduleScript) + if CastFireFunc.CastFire then + CastFireFunc = CastFireFunc.CastFire + else + CastFireFunc = nil + end + end +end + +--[=[ + +@method Blockcast +@within BaseCast + +@param Origin Vector3 -- The origin of the blockcast. +@param Size Vector3 -- The size of the blockcast. +@param Direction Vector3 -- The direction of the blockcast. +@param Velocity Vector3 | number -- The velocity of the blockcast. +@param Behavior FastCastBehavior -- The behavior data for the blockcast. + +Create a Blockcast. + +]=] +function BaseCast:Blockcast( + Origin: Vector3, + Size: Vector3, + Direction: Vector3, + Velocity: Vector3 | number, + Behavior: TypeDef.FastCastBehavior +) + Actor:SetAttribute("Tasks", Actor:GetAttribute("Tasks") + 1) + NextProjectileID += 1 + + local cast = ActiveCast.createCastData({ + Output = Output, + ActiveCastCleaner = ActiveCastCleaner, + SyncChange = SyncChanges + }, NextProjectileID, Origin, Direction, Velocity, Behavior, FastCastEventsModule, { + CastType = EnumCastTypes.Blockcast, + Size = Size + } :: any, ObjectCacheInstance, true) + + ParallelSimulation.Register(cast) + self.Actives[cast.ID] = cast + + if Behavior.FastCastEventsConfig.UseCastFire then + SendCastFire(cast, Origin, Direction, Velocity, Behavior) + end + if Behavior.FastCastEventsModuleConfig.UseCastFire and CastFireFunc then + CastFireFunc(cast, Origin, Direction, Velocity, Behavior) + end +end + +--[=[ + +@method Spherecast +@within BaseCast + +@param Origin Vector3 -- The origin of the spherecast. +@param Radius number -- The radius of the spherecast. +@param Direction Vector3 -- The direction of the spherecast. +@param Velocity Vector3 | number -- The velocity of the spherecast. +@param Behavior FastCastBehavior -- The behavior data for the spherecast. + +Create a Spherecast. + +]=] +function BaseCast:Spherecast( + Origin: Vector3, + Radius: number, + Direction: Vector3, + Velocity: Vector3 | number, + Behavior: TypeDef.FastCastBehavior +) + Actor:SetAttribute("Tasks", Actor:GetAttribute("Tasks") + 1) + NextProjectileID += 1 + + local cast = ActiveCast.createCastData({ + Output = Output, + ActiveCastCleaner = ActiveCastCleaner, + SyncChange = SyncChanges + }, NextProjectileID, Origin, Direction, Velocity, Behavior, FastCastEventsModule, { + CastType = EnumCastTypes.Spherecast, + Radius = Radius + } :: any, ObjectCacheInstance, true) + + ParallelSimulation.Register(cast) + self.Actives[cast.ID] = cast + + if Behavior.FastCastEventsConfig.UseCastFire then + SendCastFire(cast, Origin, Direction, Velocity, Behavior) + end + if Behavior.FastCastEventsModuleConfig.UseCastFire and CastFireFunc then + CastFireFunc(cast, Origin, Direction, Velocity, Behavior) + end +end + +--[=[ + @method SetMovementMode + @within BaseCast + + @param mode "BulkMoveTo" | "Motor6D" -- The movement mode to set. + @param enabled boolean -- Whether to enable or disable the movement mode. + + Sets the movement mode for the casts. This determines how the cast's position is updated during simulation. + +]=] +function BaseCast:SetMovementModeEnabled(enabled: boolean, mode: "BulkMoveTo" | "Motor6D") + CurrentMovementMode = mode + + if mode == "Motor6D" and enabled then + if not Motor6DCacheInstance then + Motor6DCacheInstance = Motor6DCache.new() + end + else + if Motor6DCacheInstance then + Motor6DCacheInstance:Destroy() + Motor6DCacheInstance = nil + end + end + + ParallelSimulation.SetMovementModeEnabled(enabled, mode) +end + +function BaseCast:BindObjectCache( + enabled: boolean, + Template: BasePart?, + CacheSize: number?, + CacheHolder: Instance? +) + if enabled then + if ObjectCacheInstance then + return + end + + if not Template then + error("Template must be provided when enabling ObjectCache.") + end + + if not CacheSize then + CacheSize = DEFAULT_CACHE_SIZE + end + + if not CacheHolder then + CacheHolder = DEFAULT_CACHE_HOLDER + end + ObjectCacheInstance = ObjectCache.new(Template, CacheSize, CacheHolder) + else + if ObjectCacheInstance then + ObjectCacheInstance:Destroy() + ObjectCacheInstance = nil + end + end +end + +--[=[ + +@method Destroy +@within BaseCast + +Destroys the BaseCast instance and cleans up resources. + +]=] +function BaseCast:Destroy() + if ParallelSimulation then + ParallelSimulation.Stop() + end + + if ObjectCacheInstance then + ObjectCacheInstance:Destroy() + end + + if Motor6DCacheInstance then + Motor6DCacheInstance:Destroy() + end + + FastCastEventsModule = nil + + if SyncChangesConnection then + SyncChangesConnection:Disconnect() + SyncChangesConnection = nil + end + + local activeList = {} + for _, v in self.Actives do + table.insert(activeList, v) + end + for _, v in activeList do + TerminateCast(v) + end + + self.Actives = {} + setmetatable(self, nil) +end + +-- Motor6D + +function BaseCast:_GetMotor6D(projectilePart: BasePart?) + if Motor6DCacheInstance and projectilePart then + return Motor6DCacheInstance:Connect(projectilePart) + end + return nil +end + +function BaseCast:_ReturnMotor6D(motor6d: Motor6D?) + if Motor6DCacheInstance then + Motor6DCacheInstance:Disconnect(motor6d) + end +end + +return BaseCast diff --git a/src/BaseCastSerial.luau b/src/BaseCastSerial.luau new file mode 100644 index 00000000..ac5efb86 --- /dev/null +++ b/src/BaseCastSerial.luau @@ -0,0 +1,313 @@ +--[[ + - Author : Mawin CK + - Date : 2025 + +]] + +local FastCast2 = script.Parent + +local SerialSimulation = require(script.Parent.SerialSimulation) +local FastCastEnums = require(FastCast2:WaitForChild("FastCastEnums")) +local ActiveCast = require(FastCast2:WaitForChild("ActiveCast")) +local TypeDef = require(FastCast2:WaitForChild("TypeDefinitions")) +local ObjectCache = require(FastCast2:WaitForChild("ObjectCache")) +local Motor6DCache = require(FastCast2:WaitForChild("Motor6DCache")) + +local EnumCastTypes = FastCastEnums.CastType + +local BaseCast = {} +BaseCast.__index = BaseCast +BaseCast.__type = "BaseCast" + +local DEFAULT_CACHE_SIZE = 500 +local DEFAULT_CACHE_HOLDER = workspace + +local NextProjectileID = 0 + +local function TerminateCast(cast: any, castTerminatingFunction: TypeDef.OnCastTerminatingFunction?) + if castTerminatingFunction then + castTerminatingFunction((cast :: any)) + end + + for key, _ in (cast :: any) do + cast[key] = nil + end +end + + +function BaseCast.Init(events: TypeDef.FastCastEvents, Data: any) + local self = setmetatable({}, BaseCast) + self.Actives = {} + self.CastFirefn = events.CastFire + self.Motor6DCacheInstance = nil + self.ObjectCacheInstance = nil + self.CurrentMovementMode = "BulkMoveTo" + + if Data.useObjectCache then + local objectCacheArgs = Data.objectCacheArgs or {} + if not objectCacheArgs.CacheSize then + objectCacheArgs.CacheSize = DEFAULT_CACHE_SIZE + end + + if not objectCacheArgs.CacheHolder then + objectCacheArgs.CacheHolder = DEFAULT_CACHE_HOLDER + end + + self.ObjectCacheInstance = ObjectCache.new(objectCacheArgs.Template, objectCacheArgs.CacheSize, objectCacheArgs.CacheHolder) :: any + end + + self.CurrentMovementMode = Data.movementMode or "BulkMoveTo" + if self.CurrentMovementMode == "Motor6D" then + self.Motor6DCacheInstance = Motor6DCache.new() + end + + self.SerialSimulation = SerialSimulation.new() + self.SerialSimulation:Init(self, events) + + self.SerialSimulation:SetMovementMode(self.CurrentMovementMode, true) + + self.SerialSimulation:Start() + + return self +end + +--[=[ + +@method Raycast +@within BaseCast + +@param Origin Vector3 -- The origin of the raycast. +@param Direction Vector3 -- The direction of the raycast. +@param Velocity Vector3 | number -- The velocity of the raycast. +@param Behavior FastCastBehavior -- The behavior data for the raycast. +@param GUID string -- The unique identifier for the raycast. + +Create a raycast. + +]=] +function BaseCast:Raycast( + Origin: Vector3, + Direction: Vector3, + Velocity: Vector3 | number, + Behavior: TypeDef.FastCastBehavior +) + NextProjectileID += 1 + local cast = ActiveCast.createCastData(self, NextProjectileID, Origin, Direction, Velocity, Behavior, nil, { + CastType = EnumCastTypes.Raycast + } :: any, self.ObjectCacheInstance) + + self.SerialSimulation:Register(cast) + self.Actives[cast.ID] = cast + + if Behavior.FastCastEventsConfig.UseCastFire and self.CastFirefn then + self.CastFirefn(cast, Origin, Direction, Velocity, Behavior) + end +end + +--[=[ + +@method Blockcast +@within BaseCast + +@param Origin Vector3 -- The origin of the raycast. +@param Size Vector3 -- The size of the raycast. +@param Direction Vector3 -- The direction of the raycast. +@param Velocity Vector3 | number -- The velocity of the raycast. +@param Behavior FastCastBehavior -- The behavior data for the raycast. + +Create a Blockcast. + +]=] +function BaseCast:Blockcast( + Origin: Vector3, + Size: Vector3, + Direction: Vector3, + Velocity: Vector3 | number, + Behavior: TypeDef.FastCastBehavior +) + NextProjectileID += 1 + + local cast = ActiveCast.createCastData(self, NextProjectileID, Origin, Direction, Velocity, Behavior, nil, { + CastType = EnumCastTypes.Blockcast, + Size = Size + } :: any, self.ObjectCacheInstance) + + self.SerialSimulation:Register(cast) + self.Actives[cast.ID] = cast + + if Behavior.FastCastEventsConfig.UseCastFire and self.CastFirefn then + self.CastFirefn(cast, Origin, Direction, Velocity, Behavior) + end +end + +--[=[ + +@method Spherecast +@within BaseCast + +@param Origin Vector3 -- The origin of the spherecast. +@param Radius number -- The radius of the spherecast. +@param Direction Vector3 -- The direction of the spherecast. +@param Velocity Vector3 | number -- The velocity of the spherecast. +@param Behavior FastCastBehavior -- The behavior data for the spherecast. + +Create a Spherecast. + +]=] +function BaseCast:Spherecast( + Origin: Vector3, + Radius: number, + Direction: Vector3, + Velocity: Vector3 | number, + Behavior: TypeDef.FastCastBehavior +) + NextProjectileID += 1 + + local cast = ActiveCast.createCastData(self, NextProjectileID, Origin, Direction, Velocity, Behavior, nil, { + CastType = EnumCastTypes.Spherecast, + Radius = Radius + } :: any, self.ObjectCacheInstance) + + self.SerialSimulation:Register(cast) + self.Actives[cast.ID] = cast + + if Behavior.FastCastEventsConfig.UseCastFire and self.CastFirefn then + self.CastFirefn(cast, Origin, Direction, Velocity, Behavior) + end +end + +--[=[ + +@method SetMovementMode +@within BaseCast + + @param mode "BulkMoveTo" | "Motor6D" -- The movement mode to set. + @param enabled boolean -- Whether to enable or disable the movement mode. + + Sets the movement mode for the casts. This determines how the cast's position is updated during simulation. + +]=] +function BaseCast:SetMovementModeEnabled(enabled: boolean, mode: "BulkMoveTo" | "Motor6D") + self.CurrentMovementMode = mode + + if mode == "Motor6D" and enabled then + if not self.Motor6DCacheInstance then + self.Motor6DCacheInstance = Motor6DCache.new() + end + else + if self.Motor6DCacheInstance then + self.Motor6DCacheInstance:Destroy() + self.Motor6DCacheInstance = nil + end + end + + self.SerialSimulation:SetMovementModeEnabled(enabled, mode) +end + +function BaseCast:BindObjectCache( + enabled: boolean, + Template: BasePart?, + CacheSize: number?, + CacheHolder: Instance? +) + if enabled then + if self.ObjectCacheInstance then + return + end + + if not Template then + error("Template must be provided when enabling ObjectCache.") + end + + if not CacheSize then + CacheSize = DEFAULT_CACHE_SIZE + end + + if not CacheHolder then + CacheHolder = DEFAULT_CACHE_HOLDER + end + self.ObjectCacheInstance = ObjectCache.new(Template, CacheSize, CacheHolder) + else + if self.ObjectCacheInstance then + self.ObjectCacheInstance:Destroy() + self.ObjectCacheInstance = nil + end + end +end + +--[=[ + +@method Destroy +@within BaseCast + +Destroys the BaseCast instance and cleans up resources. + +]=] +function BaseCast:Destroy() + if self.SerialSimulation then + self.SerialSimulation:Stop() + end + + if self.ObjectCacheInstance then + self.ObjectCacheInstance:Destroy() + end + + if self.Motor6DCacheInstance then + self.Motor6DCacheInstance:Destroy() + end + + for _, v in self.Actives do + TerminateCast(v) + end + + self.Actives = {} + setmetatable(self, nil) +end + +-- Utils + +function BaseCast:_TerminateCast(castID: number, castTerminatingfn: (...any) -> ()) + if self.Actives[castID] then + local cast = self.Actives[castID] + if castTerminatingfn then + castTerminatingfn(cast) + end + + if cast.RayInfo and cast.RayInfo.CosmeticBulletObject then + if self.ObjectCacheInstance then + self.ObjectCacheInstance:ReturnPart(cast.RayInfo.CosmeticBulletObject) + else + cast.RayInfo.CosmeticBulletObject:Destroy() + cast.RayInfo.CosmeticBulletObject = nil + end + end + + for key, _ in (cast :: any) do + cast[key] = nil + end + self.Actives[castID] = nil + end +end + +function BaseCast:_GetMotor6D(projectilePart: BasePart?) + if self.Motor6DCacheInstance and projectilePart then + return self.Motor6DCacheInstance:Connect(projectilePart) + end + return nil +end + +function BaseCast:_ReturnMotor6D(motor6d: Motor6D?) + if self.Motor6DCacheInstance then + self.Motor6DCacheInstance:Disconnect(motor6d) + end +end + +function BaseCast:_UpdateEvents(eventName: string, newEventfn: (...any) -> ()) + if eventName == "CastFire" then + self.CastFirefn = newEventfn + return + end + self.SerialSimulation:_UpdateEvents(eventName, newEventfn) +end + +return BaseCast diff --git a/src/FastCast2/Configs.luau b/src/Config.luau similarity index 79% rename from src/FastCast2/Configs.luau rename to src/Config.luau index a2e91b84..35524a08 100644 --- a/src/FastCast2/Configs.luau +++ b/src/Config.luau @@ -1,17 +1,15 @@ --[[ - Author : Mawin CK - Date : 2025 - -- Verison : 0.0.3 + ]] --- Haha, noob - local Configs = {} Configs.DebugLogging = { Casting = false, Segment = false, Hit = false, - RayPierce = false, + Pierced = false, Calculation = false, } Configs.VisualizeCasts = true diff --git a/src/FastCast2/DefaultConfigs.luau b/src/DefaultConfigs.luau similarity index 85% rename from src/FastCast2/DefaultConfigs.luau rename to src/DefaultConfigs.luau index 9dbc1f2f..8657bb19 100644 --- a/src/FastCast2/DefaultConfigs.luau +++ b/src/DefaultConfigs.luau @@ -1,7 +1,7 @@ --[[ - Author : Mawin_CK - Date : 2025 - -- Verison : 0.0.6 + ]] --!strict @@ -23,25 +23,14 @@ Defaults.FastCastBehavior = { RaycastParams = nil, Acceleration = Vector3.new(), MaxDistance = 1000, - CanPierceFunction = nil, HighFidelityBehavior = FastCastEnums.HighFidelityBehavior.Default, HighFidelitySegmentSize = 0.5, CosmeticBulletTemplate = nil, - CosmeticBulletProvider = nil, CosmeticBulletContainer = nil, AutoIgnoreContainer = true, - SimulateAfterPhysic = true, - - -- Performance - AutomaticPerformance = true, - AdaptivePerformance = { - HighFidelitySegmentSizeIncrease = 0.5, - LowerHighFidelityBehavior = true - }, - -- Debug VisualizeCasts = false, VisualizeCastSettings = { @@ -64,7 +53,7 @@ Defaults.FastCastBehavior = { Debug_RayLifetime = 1, Debug_HitLifetime = 1 }, - + FastCastEventsModuleConfig = { UseLengthChanged = false, UseHit = true, @@ -79,6 +68,7 @@ Defaults.FastCastBehavior = { UseHit = true, UsePierced = true, UseCastTerminating = true, + UseCanPierce = true, UseCastFire = true } } :: TypeDefinitions.FastCastBehavior diff --git a/src/FastCast2/ActiveCast.luau b/src/FastCast2/ActiveCast.luau deleted file mode 100644 index 4dfb4717..00000000 --- a/src/FastCast2/ActiveCast.luau +++ /dev/null @@ -1,994 +0,0 @@ --- Mozilla Public License 2.0 (files originally from FastCast) ---[[ - - Modified by: Mawin CK - - Date : 2025 - -- Verison : 0.0.9 -]] - --- NOTE: Please don't modify or changing anything --- You don't even know, what's going on --- (I also don't know what am I writing) - --- Services -local RS = game:GetService("RunService") - --- Variables -local FastCastModule = script.Parent - --- Dependencies -local FastCast = require(FastCastModule) -local TypeDef = require(FastCastModule:WaitForChild("TypeDefinitions")) -local Configs = require(FastCastModule:WaitForChild("Configs")) -local DebugLogging = Configs.DebugLogging -local FastCastEnums = require(FastCastModule:WaitForChild("FastCastEnums")) --- Constants -local FC_VIS_OBJ_NAME = "FastCastVisualizationObjects" -local MAX_SEGMENT_CAL_TIME = 0.016 * 5 -- 80ms -local MAX_CASTING_TIME = 0.2 -- 200ms - -local DEFAULT_MAX_DISTANCE = 1000 - --- Enums -local EnumCastTypes = FastCastEnums.CastType - --- Debugging -local DBG_SEGMENT_SUB_COLOR = Color3.new(0.286275, 0.329412, 0.247059) -local DBG_SEGMENT_SUB_COLOR2 = Color3.new(0.454902, 0.933333, 0.011765) - -local DBG_HIT_SUB_COLOR = Color3.new(0.0588235, 0.87451, 1) - -local DBG_RAYPIERCE_SUB_COLOR = Color3.new(1, 0.113725, 0.588235) - --- Types -type vaildcast = TypeDef.ActiveCastData | TypeDef.ActiveBlockcastData | TypeDef.ActiveSpherecastData - -type BlockcastVariant = { CastType: number, Size: Vector3} -type SpherecastVariant = { CastType: number, Radius: number } -type CastVariants = BlockcastVariant | SpherecastVariant - -type RayVisualizerVariant = { castLength: number} -type BlockVisualizerVariant = { size: Vector3 } -type SphereVisualizerVariant = { radius: number } -type CastVisualizerVariants = RayVisualizerVariant | BlockVisualizerVariant | SphereVisualizerVariant - -type CastHandler = (WorldRoot: WorldRoot, origin: Vector3, direction: Vector3, castVariant: CastVariants) -> RaycastResult -type CastVisualizer = (castStartCFrame: CFrame, VisualizeCasts: boolean, VisualizeCastSettings: TypeDef.VisualizeCastSettings, castVariant: CastVisualizerVariants) -> (ConeHandleAdornment | BoxHandleAdornment | SphereHandleAdornment)? - --- I have no ideas, what I'm doing --- Automatic Performance setting -local HIGH_FIDE_INCREASE_SIZE = 0.5 - --- Is this even useful? --- What Is even these magic numbers? -local CastVariantTypes = { - [EnumCastTypes.Raycast] = "Raycast", - [EnumCastTypes.Blockcast] = "Blockcast", - [EnumCastTypes.Spherecast] = "Spherecast" -} - -local castHandlers = { - [EnumCastTypes.Raycast] = function( - targetWorldRoot: WorldRoot, - origin: Vector3, - direction: Vector3, - parameters: RaycastParams - ) - return targetWorldRoot:Raycast(origin, direction, parameters) - end, - [EnumCastTypes.Blockcast] = function( - targetWorldRoot: WorldRoot, - origin: Vector3, - direction: Vector3, - parameters: RaycastParams, - variant: BlockcastVariant - ) - return targetWorldRoot:Blockcast(CFrame.new(origin), variant.Size, direction, parameters) - end, - [EnumCastTypes.Spherecast] = function( - targetWorldRoot: WorldRoot, - origin: Vector3, - direction: Vector3, - parameters: RaycastParams, - variant: SpherecastVariant - ) - return targetWorldRoot:Spherecast(origin, variant.Radius, direction, parameters) - end -} - ---[=[ - @class ActiveCast - - An ActiveCast represents a bullet fired by a parent [Caster](Caster). It contains methods of accessing the physics - data of this specific bullet at any given time, as well as methods to alter its trajectory during runtime. -]=] - -local ActiveCast = {} - -local function DebrisAdd(obj: Instance, Lifetime: number) - if not obj then - return - end - if Lifetime <= 0 then - obj:Destroy() - end - - task.delay(Lifetime, function() - obj:Destroy() - end) -end - -local function GetPositionAtTime( - t: number, - origin: Vector3, - initialVelocity: Vector3, - acceleration: Vector3 -): Vector3 - local force = - Vector3.new((acceleration.X * t ^ 2) / 2, (acceleration.Y * t ^ 2) / 2, (acceleration.Z * t ^ 2) / 2) - return origin + (initialVelocity * t) + force -end - -local function GetVelocityAtTime(time: number, initialVelocity: Vector3, acceleration: Vector3): Vector3 - return initialVelocity + acceleration * time -end - -local function CloneCastParams(params: RaycastParams): RaycastParams - local clone: RaycastParams = RaycastParams.new() - clone.CollisionGroup = params.CollisionGroup - clone.FilterType = params.FilterType - clone.FilterDescendantsInstances = params.FilterDescendantsInstances - clone.IgnoreWater = params.IgnoreWater - return clone -end - -local function GetFastCastVisualizationContainer(): Instance - local fcVisualizationObjects = workspace.Terrain:FindFirstChild(FC_VIS_OBJ_NAME) - if fcVisualizationObjects then - return fcVisualizationObjects - end - - fcVisualizationObjects = Instance.new("Folder") - fcVisualizationObjects.Name = FC_VIS_OBJ_NAME - fcVisualizationObjects.Archivable = false - fcVisualizationObjects.Parent = workspace.Terrain - return fcVisualizationObjects -end - ---[[ -local function GetTrajectoryInfo( - cast: TypeDef.ActiveCastData | TypeDef.ActiveBlockCast, - index: number -): { [number]: Vector3 } - assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED") - local trajectories = cast.StateInfo.Trajectories - local trajectory = trajectories[index] - local duration = trajectory.EndTime - trajectory.StartTime - - local origin = trajectory.Origin - local vel = trajectory.InitialVelocity - local accel = trajectory.Acceleration - - return { GetPositionAtTime(duration, origin, vel, accel), GetVelocityAtTime(duration, vel, accel) } -end - -local function GetLatestTrajectoryEndInfo(cast: TypeDef.ActiveCastData): { [number]: Vector3 } - return GetTrajectoryInfo(cast, #cast.StateInfo.Trajectories) -end -]] - --- Debugging - -local function DbgVisualizeRaySegment( - castStartCFrame: CFrame, - VisualizeCasts: boolean, - VisualizeCastSettings: TypeDef.VisualizeCastSettings, - variant: RayVisualizerVariant -): ConeHandleAdornment? - if not VisualizeCasts then - return - end - local adornment = Instance.new("ConeHandleAdornment") - adornment.Adornee = workspace.Terrain - adornment.CFrame = castStartCFrame - adornment.Height = variant.castLength - adornment.Color3 = VisualizeCastSettings.Debug_SegmentColor - adornment.Radius = VisualizeCastSettings.Debug_SegmentSize - adornment.Transparency = VisualizeCastSettings.Debug_SegmentTransparency - adornment.Parent = GetFastCastVisualizationContainer() - - DebrisAdd(adornment, VisualizeCastSettings.Debug_RayLifetime) - return adornment -end - -local function DbgVisualizeBlockSegment( - castStartCFrame: CFrame, - VisualizeCasts: boolean, - VisualizeCastSetting: TypeDef.VisualizeCastSettings, - variant: BlockVisualizerVariant -): BoxHandleAdornment? - if not VisualizeCasts then - return - end - local adornment = Instance.new("BoxHandleAdornment") - adornment.Adornee = workspace.Terrain - adornment.CFrame = castStartCFrame - --adornment.Height = castLength - - adornment.Size = variant.size - adornment.Color3 = VisualizeCastSetting.Debug_SegmentColor - adornment.Transparency = VisualizeCastSetting.Debug_SegmentTransparency - - adornment.Parent = GetFastCastVisualizationContainer() - - DebrisAdd(adornment, VisualizeCastSetting.Debug_RayLifetime) - return adornment -end - -local function DbgVisualizeSphereSegment( - castStartCFrame: CFrame, - VisualizeCasts: boolean, - VisualizeCastSetting: TypeDef.VisualizeCastSettings, - variant: SphereVisualizerVariant -): SphereHandleAdornment? - if not VisualizeCasts then - return - end - local adornment = Instance.new("SphereHandleAdornment") - adornment.Adornee = workspace.Terrain - adornment.CFrame = castStartCFrame - --adornment.Height = castLength - adornment.Radius = variant.radius - --adornment.Size = Vector3.new(size.X, size.Y, size.Z + castLength) - adornment.Color3 = VisualizeCastSetting.Debug_SegmentColor - adornment.Transparency = VisualizeCastSetting.Debug_SegmentTransparency - - adornment.Parent = GetFastCastVisualizationContainer() - - DebrisAdd(adornment, VisualizeCastSetting.Debug_RayLifetime) - return adornment -end - -local function DbgVisualizeHit( - atCF: CFrame, - wasPierce: boolean, - VisualizeCasts: boolean, - VisualizeCastSettings: TypeDef.VisualizeCastSettings -): SphereHandleAdornment? - if not VisualizeCasts then - return - end - local adornment = Instance.new("SphereHandleAdornment") - adornment.Adornee = workspace.Terrain - adornment.CFrame = atCF - -- Alert! someone is Mawining it!!!!! - adornment.Radius = (wasPierce == false) and VisualizeCastSettings.Debug_HitSize - or VisualizeCastSettings.Debug_RayPierceSize - adornment.Transparency = (wasPierce == false) and VisualizeCastSettings.Debug_HitTransparency - or VisualizeCastSettings.Debug_RayPierceTransparency - adornment.Color3 = (wasPierce == false) and VisualizeCastSettings.Debug_HitColor - or VisualizeCastSettings.Debug_RayPierceColor - adornment.Parent = GetFastCastVisualizationContainer() - - DebrisAdd(adornment, VisualizeCastSettings.Debug_HitLifetime) - return adornment -end - -local Visualizers = { - [EnumCastTypes.Raycast] = DbgVisualizeRaySegment, - [EnumCastTypes.Blockcast] = DbgVisualizeBlockSegment, - [EnumCastTypes.Spherecast] = DbgVisualizeSphereSegment -} - --- Send signals - -local function SendHit( - cast: vaildcast, - resultOfCast: RaycastResult, - segmentVelocity: Vector3, - cosmeticBulletObject: Instance? -) - --cast.Caster.RayHit:Fire(cast, resultOfCast, segmentVelocity, cosmeticBulletObject) - --cast.CasterBindable:Fire("RayHit", cast, resultOfCast, segmentVelocity, cosmeticBulletObject) - --cast.Definition.OnRayHit(cast, resultOfCast, segmentVelocity, cosmeticBulletObject) - - local FastCastEventsConfig = cast.StateInfo.FastCastEventsConfig - if FastCastEventsConfig and FastCastEventsConfig.UseHit == false then - return - end - cast.Caster.Output:Fire("Hit", cast, resultOfCast, segmentVelocity, cosmeticBulletObject) -end - -local function SendPierced( - cast: vaildcast, - resultOfCast: RaycastResult, - segmentVelocity: Vector3, - cosmeticBulletObject: Instance? -) - --cast.Caster.RayPierced:Fire(cast, resultOfCast, segmentVelocity, cosmeticBulletObject) - --cast.CasterBindable:Fire("RayPierced", cast, resultOfCast, segmentVelocity, cosmeticBulletObject) - --cast.Definition.OnRayPierce(ActiveCast, resultOfCast, segmentVelocity, cosmeticBulletObject) - local FastCastEventsConfig = cast.StateInfo.FastCastEventsConfig - if FastCastEventsConfig and FastCastEventsConfig.UsePierced == false then - return - end - cast.Caster.Output:Fire("Pierced", cast, resultOfCast, segmentVelocity, cosmeticBulletObject) -end - -local function SendLengthChanged( - cast: vaildcast, - lastPoint: Vector3, - rayDir: Vector3, - rayDisplacement: number, - segmentVelocity: Vector3, - cosmeticBulletObject: Instance? -) - --cast.Caster.LengthChanged:Fire(cast, lastPoint, rayDir, rayDisplacement, cosmeticBulletObject) - --cast.Definition.OnLengthChanged(ActiveCast, lastPoint, rayDir, rayDisplacement, segmentVelocity, cosmeticBulletObject) - --cast.Caster.LengthChanged:Fire(ActiveCast, lastPoint, rayDir, rayDisplacement, segmentVelocity, cosmeticBulletObject) - - --print(cast.Caster.Output) - local FastCastEventsConfig = cast.StateInfo.FastCastEventsConfig - if FastCastEventsConfig and FastCastEventsConfig.UseLengthChanged == false then - return - end - cast.Caster.Output:Fire( - "LengthChanged", - cast, - lastPoint, - rayDir, - rayDisplacement, - segmentVelocity, - cosmeticBulletObject - ) -end - ---[[local function SendCastFire( - cast: TypeDef.ActiveCast, - origin: Vector3, - direction: Vector3, - velocity: Vector3 | number, - behavior: TypeDef.FastCastBehavior -) - cast.Caster.Output:Fire("CastFire", cast, origin, direction, velocity, behavior) -end]] - -local function SimulateCast( - cast: any, - delta: number, - FastCastEvents: TypeDef.FastCastEvents, - variant: CastVariants -) - assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED") - - --PrintDebug("Casting for frame.") - --print("1C") - if DebugLogging.Casting then - print("Casting for frame.") - end - - local latestTrajectory = cast.StateInfo.Trajectories[#cast.StateInfo.Trajectories] - - local origin = latestTrajectory.Origin - local totalDelta = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime - local initialVelocity = latestTrajectory.InitialVelocity - local acceleration = latestTrajectory.Acceleration - - local lastPoint = GetPositionAtTime(totalDelta, origin, initialVelocity, acceleration) - --local lastVelocity = GetVelocityAtTime(totalDelta, initialVelocity, acceleration) - local lastDelta = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime - - cast.StateInfo.TotalRuntime += delta - - totalDelta = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime - - local currentTarget = GetPositionAtTime(totalDelta, origin, initialVelocity, acceleration) - local segmentVelocity = GetVelocityAtTime(totalDelta, initialVelocity, acceleration) - local totalDisplacement = currentTarget - lastPoint - - local rayDir = totalDisplacement.Unit * segmentVelocity.Magnitude * delta - - local CastType = variant.CastType - - local targetWorldRoot = cast.RayInfo.WorldRoot - - local CastHandler = castHandlers[CastType] - local Visualizer = Visualizers[CastType] - - local resultOfCast = CastHandler(targetWorldRoot, lastPoint, rayDir, cast.RayInfo.Parameters, variant) - - local point = currentTarget - local part: Instance? = nil - --local material = Enum.Material.Air - --local normal = Vector3.new() - - if resultOfCast ~= nil then - point = resultOfCast.Position - part = resultOfCast.Instance - --material = resultOfCast.Material - --normal = resultOfCast.Normal - end - - local rayDisplacement = (point - lastPoint).Magnitude - - local VisualizeCasts = cast.StateInfo.VisualizeCasts - local VisualizeCastSettings = cast.StateInfo.VisualizeCastSettings - - local FastCastEventsModuleConfig = cast.StateInfo.FastCastEventsModuleConfig - - if typeof(latestTrajectory.Acceleration) ~= "Vector3" then - latestTrajectory.Acceleration = Vector3.new() - end - - local VisualizeVariant = {} - - if CastType == EnumCastTypes.Raycast then - VisualizeVariant.castLength = rayDisplacement - elseif CastType == EnumCastTypes.Blockcast then - VisualizeVariant.size = cast.RayInfo.Size - elseif CastType == EnumCastTypes.Spherecast then - VisualizeVariant.radius = cast.RayInfo.Radius - end - - cast.CFrame = CFrame.new(lastPoint, lastPoint + rayDir) * CFrame.new(0, 0, -rayDisplacement / 2) - - task.synchronize() - - local LengthChangedfn: TypeDef.OnLengthChangedFunction? = nil - local canPierceCheckfn: TypeDef.CanPierceFunction? = nil - local castTerminatingfn: TypeDef.OnCastTerminatingFunction? = nil - local Hitfn: TypeDef.OnHitFunction? = nil - local Piercedfn: TypeDef.OnPiercedFunction? = nil - - if FastCastEvents then - canPierceCheckfn = FastCastEventsModuleConfig.UseCanPierce and FastCastEvents.CanPierce or nil - castTerminatingfn = FastCastEventsModuleConfig.UseCastTerminating and FastCastEvents.CastTerminating or nil - Hitfn = FastCastEventsModuleConfig.UseHit and FastCastEvents.Hit or nil - Piercedfn = FastCastEventsModuleConfig.UsePierced and FastCastEvents.Pierced or nil - LengthChangedfn = FastCastEventsModuleConfig.UseLengthChanged and FastCastEvents.LengthChanged or nil - end - - SendLengthChanged(cast, lastPoint, rayDir.Unit, rayDisplacement, segmentVelocity, cast.RayInfo.CosmeticBulletObject) - - if LengthChangedfn then - LengthChangedfn( - cast, - lastPoint, - rayDir.Unit, - rayDisplacement, - segmentVelocity, - cast.RayInfo.CosmeticBulletObject - ) - end - - cast.StateInfo.DistanceCovered += rayDisplacement - - local rayVisualization: ConeHandleAdornment? = nil - - if delta > 0 then - rayVisualization = Visualizer( - CFrame.new(lastPoint, lastPoint + rayDir), - VisualizeCasts, - VisualizeCastSettings, - VisualizeVariant - ) - end - - -- I feel so good - - -- NOTE: Please dont remove "part and" - -- Why? basically when part doesn't exist it will do nothing, but removing "part and" will break the logic - -- You can't do anything about it - if part and part ~= cast.RayInfo.CosmeticBulletObject then - - if DebugLogging.Hit then - print("Hit something, testing now.") - end - - if DebugLogging.RayPierce and canPierceCheckfn == nil then - print("No piercing function set, proceeding to hit processing.") - end - - if - canPierceCheckfn == nil - or canPierceCheckfn(cast, resultOfCast, segmentVelocity, cast.RayInfo.CosmeticBulletObject) == false - then - --PrintDebug("Piercing function is nil or it returned FALSE to not pierce this hit.") - - if DebugLogging.RayPierce then - print("Piercing function is nil or it returned FALSE to not pierce this hit.") - end - - cast.StateInfo.IsActivelySimulatingPierce = false - - if - cast.StateInfo.HighFidelityBehavior == FastCastEnums.HighFidelityBehavior.Automatic - and cast.StateInfo.HighFidelitySegmentSize > 0 - then - --print("2CR") - cast.StateInfo.CancelHighResCast = false - - if cast.StateInfo.IsActivelyResimulating then - FastCast:TerminateCast(cast, castTerminatingfn) - - warn( - "Cascading cast lag encountered! The caster attempted to perform a high fidelity cast before the previous one completed, resulting in exponential cast lag. Consider increasing HighFidelitySegmentSize." - ) - return - end - - cast.StateInfo.IsActivelyResimulating = true - - --PrintDebug("Hit was registered, but recalculation is on for physics based casts. Recalculating to verify a real hit...") - - if DebugLogging.Calculation then - print( - "Hit was registered, but recalculation is on for physics based casts. Recalculating to verify a real hit..." - ) - end - - local numSegmentsDecimal = rayDisplacement / cast.StateInfo.HighFidelitySegmentSize - local numSegmentsReal = math.floor(numSegmentsDecimal) - --local realSegmentLength = rayDisplacement / numSegmentsReal - - if numSegmentsReal == 0 then - numSegmentsReal = 1 - end - - local timeIncrement = delta / numSegmentsReal - - if DebugLogging.Calculation then - print( - "Performing subcast! Time increment: " .. timeIncrement .. ", num segments: " .. numSegmentsReal - ) - end - - for segmentIndex = 1, numSegmentsReal do - if cast.StateInfo.CancelHighResCast then - cast.StateInfo.CancelHighResCast = false - break - end - - local subPosition = GetPositionAtTime( - lastDelta + (timeIncrement * segmentIndex), - origin, - initialVelocity, - acceleration - ) - local subVelocity = - GetVelocityAtTime(lastDelta + (timeIncrement * segmentIndex), initialVelocity, acceleration) - local subRayDir = subVelocity * delta - local subResult = CastHandler(targetWorldRoot, subPosition, subRayDir, cast.RayInfo.Parameters, variant) - - local subDisplacement = (subPosition - (subPosition + subVelocity)).Magnitude - - if CastType == EnumCastTypes.Raycast then - VisualizeVariant.castLength = subDisplacement - end - - -- What? - if subResult ~= nil then - subDisplacement = (subPosition - subResult.Position).Magnitude - local dbgSeg = Visualizer( - CFrame.new(subPosition, subPosition + subVelocity), - VisualizeCasts, - VisualizeCastSettings, - VisualizeVariant - ) - if dbgSeg ~= nil then - dbgSeg.Color3 = DBG_SEGMENT_SUB_COLOR - end - - if - canPierceCheckfn == nil - or canPierceCheckfn(cast, subResult, subVelocity, cast.RayInfo.CosmeticBulletObject) - == false - then - cast.StateInfo.IsActivelyResimulating = false - - SendHit(cast, subResult, subVelocity, cast.RayInfo.CosmeticBulletObject) - if Hitfn then - Hitfn(cast, subResult, subVelocity, cast.RayInfo.CosmeticBulletObject) - end - FastCast:TerminateCast(cast, castTerminatingfn) - - local vis = DbgVisualizeHit(CFrame.new(point), false, VisualizeCasts, VisualizeCastSettings) - if vis ~= nil then - vis.Color3 = DBG_HIT_SUB_COLOR - end - - return - else - SendPierced(cast, subResult, subVelocity, cast.RayInfo.CosmeticBulletObject) - if Piercedfn then - Piercedfn(cast, subResult, subVelocity, cast.RayInfo.CosmeticBulletObject) - end - - local vis = DbgVisualizeHit(CFrame.new(point), true, VisualizeCasts, VisualizeCastSettings) - if vis ~= nil then - vis.Color3 = DBG_RAYPIERCE_SUB_COLOR - end - --if (dbgSeg ~= nil) then dbgSeg.Color3 = DBG_RAYPIERCE_SEGMENT_COLOR end - end - else - local dbgSeg = Visualizer( - CFrame.new(subPosition, subPosition + subVelocity), - VisualizeCasts, - VisualizeCastSettings, - VisualizeVariant - ) - if dbgSeg ~= nil then - dbgSeg.Color3 = DBG_SEGMENT_SUB_COLOR2 - end - end - - if DebugLogging.Segment then - print("[" .. segmentIndex .. "] Subcast of time increment " .. timeIncrement) - end - end - - cast.StateInfo.IsActivelyResimulating = false - --elseif (cast.StateInfo.HighFidelityBehavior ~= 1 and cast.StateInfo.HighFidelityBehavior ~= 3) then - -- cast:Terminate() - -- error("Invalid value " .. (cast.StateInfo.HighFidelityBehavior) .. " for HighFidelityBehavior.") - else - --print("1CR") - --PrintDebug("Hit was successful. Terminating.") - - if DebugLogging.Hit then - print("Hit was successful. Terminating.") - end - - SendHit(cast, resultOfCast, segmentVelocity, cast.RayInfo.CosmeticBulletObject) - if Hitfn then - Hitfn(cast, resultOfCast, segmentVelocity, cast.RayInfo.CosmeticBulletObject) - end - FastCast:TerminateCast(cast, castTerminatingfn) - - DbgVisualizeHit(CFrame.new(point), false, VisualizeCasts, VisualizeCastSettings) - return - end - else - --PrintDebug("Piercing function returned TRUE to pierce this part.") - - if DebugLogging.RayPierce then - print("Piercing function returned TRUE to pierce this part.") - end - - if rayVisualization ~= nil then - rayVisualization.Color3 = Color3.new(0.4, 0.05, 0.05) - end - DbgVisualizeHit(CFrame.new(point), true, VisualizeCasts, VisualizeCastSettings) - SendPierced(cast, resultOfCast, segmentVelocity, cast.RayInfo.CosmeticBulletObject) - if Piercedfn then - Piercedfn(cast, resultOfCast, segmentVelocity, cast.RayInfo.CosmeticBulletObject) - end - end - end - - if cast.StateInfo.DistanceCovered >= cast.RayInfo.MaxDistance then - FastCast:TerminateCast(cast, castTerminatingfn) - - DbgVisualizeHit(CFrame.new(currentTarget), false, VisualizeCasts, VisualizeCastSettings) - end -end - ---[=[ - @function createCastData - @private - @within ActiveCast - - Creates a new ActiveCast instance with the given parameters. - Don't use this method! Instead, use [Caster:RaycastFire()](TypeDefinitions#Caster) to create ActiveCasts. - - @param BaseCast TypeDef.BaseCastData -- The base cast data used to initialize the active cast. - - @param activeCastID string -- Unique identifier for this active cast. - - @param origin Vector3 -- The starting position of the cast. - - @param direction Vector3 -- The direction the cast will travel in. - - @param velocity Vector3 | number -- The velocity of the cast (either directional or scalar). - - @param behavior TypeDef.FastCastBehavior -- The FastCast behavior configuration. - - @param eventModule TypeDef.FastCastEventsModule -- The event module to use for this cast. - - @return ActiveCastData -- The newly created ActiveCastData. -]=] -function ActiveCast.createCastData( - BaseCast: TypeDef.BaseCastData, - activeCastID: number, - origin: Vector3, - direction: Vector3, - velocity: Vector3 | number, - behavior: TypeDef.FastCastBehavior, - eventModule: TypeDef.FastCastEventsModule?, - variant: CastVariants -): vaildcast - if typeof(velocity) == "number" then - velocity = direction.Unit * velocity - end - - if behavior.HighFidelitySegmentSize <= 0 then - error("Cannot set FastCastBehavior.HighFidelitySegmentSize <= 0!", 0) - end - - -- This world is cruel, and I must accept it. - if behavior.HighFidelityBehavior <= 0 then - behavior.HighFidelityBehavior = 1 - elseif behavior.HighFidelityBehavior >= 4 then - behavior.HighFidelityBehavior = 3 - end - - local cast = { - Caster = BaseCast, - - StateInfo = { - UpdateConnection = nil, - Paused = false, - TotalRuntime = 0, - DistanceCovered = 0, - HighFidelitySegmentSize = behavior.HighFidelitySegmentSize, - HighFidelityBehavior = behavior.HighFidelityBehavior, - IsActivelySimulatingPierce = false, - IsActivelyResimulating = false, - CancelHighResCast = false, - Trajectories = { - { - StartTime = 0, - EndTime = -1, - Origin = origin, - InitialVelocity = velocity, - Acceleration = behavior.Acceleration, - }, - }, - VisualizeCasts = behavior.VisualizeCasts, - VisualizeCastSettings = behavior.VisualizeCastSettings, - - FastCastEventsModuleConfig = { - UseLengthChanged = behavior.FastCastEventsModuleConfig.UseLengthChanged, - UseHit = behavior.FastCastEventsModuleConfig.UseHit, - UsePierced = behavior.FastCastEventsModuleConfig.UsePierced, - UseCastTerminating = behavior.FastCastEventsModuleConfig.UseCastTerminating, - UseCanPierce = behavior.FastCastEventsModuleConfig.UseCanPierce, - }, - - FastCastEventsConfig = { - UseLengthChanged = behavior.FastCastEventsConfig.UseLengthChanged, - UseHit = behavior.FastCastEventsConfig.UseHit, - UsePierced = behavior.FastCastEventsConfig.UsePierced, - UseCastTerminating = behavior.FastCastEventsConfig.UseCastTerminating, - }, - }, - - RayInfo = { - Parameters = behavior.RaycastParams, - WorldRoot = workspace, - MaxDistance = behavior.MaxDistance or DEFAULT_MAX_DISTANCE, - CosmeticBulletObject = behavior.CosmeticBulletTemplate, - FastCastEventsModule = eventModule - }, - - UserData = {}, - - Type = CastVariantTypes[variant.CastType], - CFrame = CFrame.new(origin) :: CFrame, - ID = activeCastID - } :: any - - if variant.CastType == EnumCastTypes.Blockcast then - cast.RayInfo.Size = (variant :: BlockcastVariant).Size - elseif variant.CastType == EnumCastTypes.Spherecast then - cast.RayInfo.Radius = (variant :: SpherecastVariant).Radius - end - - if behavior.UserData then - cast.UserData = behavior.UserData - end - - if cast.RayInfo.Parameters ~= nil then - cast.RayInfo.Parameters = CloneCastParams(cast.RayInfo.Parameters) - else - cast.RayInfo.Parameters = RaycastParams.new() - end - - -- CosmeticBulletObject GET - - local targetContainer: Instance? - if cast.Caster.ObjectCache then - --[[if cast.RayInfo.CosmeticBulletObject ~= nil then - warn("ObjectCache already handle that for you, Template Dupe") - end]] - - -- 1 kebab please - cast.RayInfo.CosmeticBulletObject = cast.Caster.ObjectCache:Invoke(CFrame.new(origin, origin + direction)) - targetContainer = cast.Caster.CacheHolder - else - if cast.RayInfo.CosmeticBulletObject ~= nil then - local basePart = cast.RayInfo.CosmeticBulletObject - basePart = basePart:Clone() - basePart.CFrame = CFrame.new(origin, origin + direction) - basePart.Parent = behavior.CosmeticBulletContainer - - cast.RayInfo.CosmeticBulletObject = basePart - end - - if behavior.CosmeticBulletContainer then - targetContainer = behavior.CosmeticBulletContainer - end - end - - -- the rest? :P - - if behavior.AutoIgnoreContainer == true and targetContainer ~= nil then - local igroneList = cast.RayInfo.Parameters.FilterDescendantsInstances - if not table.find(igroneList, targetContainer) then - table.insert(igroneList, targetContainer) - cast.RayInfo.Parameters.FilterDescendantsInstances = igroneList - end - end - - --SendCastFire(cast, origin, direction, velocity, behavior) - - local event - if RS:IsClient() then - event = behavior.SimulateAfterPhysic and RS.Heartbeat or RS.PreSimulation - else - event = RS.Heartbeat - end - - local FastCastEvents: TypeDef.FastCastEvents = eventModule and require(eventModule) or nil - - --setmetatable(cast, ActiveCast) - - local function Stepped(delta: number) - if cast.StateInfo.Paused then - return - end - - --PrintDebug("Casting for frame.") - - if DebugLogging.Casting then - print("Casting for frame.") - end - - local Cast_timeAtStart = tick() - - local latestTrajectory = cast.StateInfo.Trajectories[#cast.StateInfo.Trajectories] - - if typeof(latestTrajectory.Acceleration) ~= "Vector3" then - latestTrajectory.Acceleration = Vector3.new() - end - - if - cast.StateInfo.HighFidelityBehavior == FastCastEnums.HighFidelityBehavior.Always - and cast.StateInfo.HighFidelitySegmentSize > 0 - then - local Segment_timeAtStart = tick() - - local castTerminatingfn: TypeDef.OnCastTerminatingFunction? = nil - if FastCastEvents then - castTerminatingfn = cast.StateInfo.FastCastEventsModuleConfig.UseCastTerminating - and FastCastEvents.CastTerminating - or nil - end - if cast.StateInfo.IsActivelyResimulating then - FastCast:TerminateCast(cast, castTerminatingfn) - - warn( - "Cascading cast lag encountered! The caster attempted to perform a high fidelity cast before the previous one completed, resulting in exponential cast lag. Consider increasing HighFidelitySegmentSize." - ) - return - end - - cast.StateInfo.IsActivelyResimulating = true - - local origin = latestTrajectory.Origin - local totalDelta = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime - local initialVelocity = latestTrajectory.InitialVelocity - local acceleration = latestTrajectory.Acceleration - - local lastPoint = GetPositionAtTime(totalDelta, origin, initialVelocity, acceleration) - --local lastVelocity = GetVelocityAtTime(totalDelta, initialVelocity, acceleration) - --local lastDelta = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime - - cast.StateInfo.TotalRuntime += delta - - totalDelta = cast.StateInfo.TotalRuntime - latestTrajectory.StartTime - - local currentPoint = GetPositionAtTime(totalDelta, origin, initialVelocity, acceleration) - local currentVelocity = GetVelocityAtTime(totalDelta, initialVelocity, acceleration) - local totalDisplacement = currentPoint - lastPoint - - local rayDir = totalDisplacement.Unit * currentVelocity.Magnitude * delta - - local targetWorldRoot = cast.RayInfo.WorldRoot - - -- Is this how it works? - local CastHandler = castHandlers[variant.CastType] - - local resultOfCast = CastHandler(targetWorldRoot, lastPoint, rayDir, cast.RayInfo.Parameters, variant) - - local point = currentPoint - - if resultOfCast ~= nil then - point = resultOfCast.Position - end - - local rayDisplacement = (point - lastPoint).Magnitude - - cast.StateInfo.TotalRuntime -= delta - - local numSegmentsDecimal = rayDisplacement / cast.StateInfo.HighFidelitySegmentSize - local numSegmentsReal = math.floor(numSegmentsDecimal) - if numSegmentsReal == 0 then - numSegmentsReal = 1 - end - - local timeIncrement = delta / numSegmentsReal - - if DebugLogging.Calculation then - print("Performing subcast! Time increment: " .. timeIncrement .. ", num segments: " .. numSegmentsReal) - end - - for segmentIndex = 1, numSegmentsReal do - if next(cast) == nil then - return - end - if cast.StateInfo.CancelHighResCast then - cast.StateInfo.CancelHighResCast = false - break - end - - if DebugLogging.Segment then - print("[" .. segmentIndex .. "] Subcast of time increment " .. timeIncrement) - end - - --PrintDebug("[" .. segmentIndex .. "] Subcast of time increment " .. timeIncrement) - SimulateCast(cast, timeIncrement, FastCastEvents, variant) - end - - if next(cast) == nil then - return - end - cast.StateInfo.IsActivelyResimulating = false - - if - behavior.AutomaticPerformance - and (tick() - Segment_timeAtStart) > MAX_SEGMENT_CAL_TIME - and cast.StateInfo - then - local HighFideSizeAmount = behavior.AdaptivePerformance.HighFidelitySegmentSizeIncrease - or HIGH_FIDE_INCREASE_SIZE - - if DebugLogging.AutomaticPerformance then - warn("AutomaticPerformance increasing size of HighFidelitySize by : ", HighFideSizeAmount) - end - - cast.StateInfo.HighFidelitySegmentSize += HighFideSizeAmount - end - else - SimulateCast(cast, delta, FastCastEvents, variant) - end - - if - behavior.AutomaticPerformance - and behavior.AdaptivePerformance.LowerHighFidelityBehavior - and (tick() - Cast_timeAtStart) > MAX_CASTING_TIME - and cast.StateInfo - then - if cast.StateInfo.HighFidelityBehavior > 1 then - cast.StateInfo.HighFidelityBehavior -= 1 - end - end - end - - cast.StateInfo.UpdateConnection = event:ConnectParallel(Stepped) - - return cast -end - --- Will I ever be free - -return ActiveCast diff --git a/src/FastCast2/BaseCast.luau b/src/FastCast2/BaseCast.luau deleted file mode 100644 index c0b98fcc..00000000 --- a/src/FastCast2/BaseCast.luau +++ /dev/null @@ -1,378 +0,0 @@ ---[[ - - Author : Mawin CK - - Date : 2025 - -- Verison : 0.0.9 -]] - --- Services ---local HTTPS = game:GetService("HttpService") -local RS = game:GetService("RunService") - --- Requires -local FastCast2 = script.Parent -local FastCastM = require(FastCast2) - -local FastCastEnums = require(FastCast2:WaitForChild("FastCastEnums")) -local TypeDef = require(FastCast2:WaitForChild("TypeDefinitions")) ---local Signal = require(FastCast2:WaitForChild("Signal")) -local ActiveCast = require(FastCast2:WaitForChild("ActiveCast")) -local FastCastEventsModule: ModuleScript? = nil - --- Enums -local EnumCastTypes = FastCastEnums.CastType - - ---[=[ - -@class BaseCast -@private -Base class for all Raycast operations. - -]=] -local BaseCast = {} -BaseCast.__index = BaseCast -BaseCast.__type = "BaseCast" - --- Connections -local BulkMoveToConnection: RBXScriptConnection? = nil - --- Variables --- Because each different threads have different BaseCast haha -local Actives: any = {} -local Actor = nil -local Output = nil -local ActiveCastCleaner: BindableEvent = nil -local ObjectCache: BindableEvent? = nil -local NextProjectileID = 0 -local SyncChanges: BindableEvent = nil -local CastFireFunc = nil - --- Private functions - -local function HandleBulkMoveTo() - local Parts: { BasePart } = {} - local CFrames: { CFrame } = {} - - for _, ActiveCasts in Actives do - local ProjectilePart = ActiveCasts.RayInfo.CosmeticBulletObject - if not ProjectilePart then - continue - end - - local resultCFrame = ActiveCasts.CFrame - if ProjectilePart:IsA("BasePart") then - table.insert(Parts, ProjectilePart) - table.insert(CFrames, resultCFrame) - else - ProjectilePart:PivotTo(resultCFrame) - end - end - - task.synchronize() - - --print("BulkMoveTo parts : ", Parts, "CFrames : ", CFrames) - workspace:BulkMoveTo(Parts, CFrames, Enum.BulkMoveMode.FireCFrameChanged) -end - -local function SendCastFire( - cast: TypeDef.ActiveCastData, - origin: Vector3, - direction: Vector3, - velocity: Vector3 | number, - behavior: TypeDef.FastCastBehavior -) - cast.Caster.Output:Fire("CastFire", cast, origin, direction, velocity, behavior) -end - ---[=[ - -@function Init -@within BaseCast - -@param BindableOutput BindableEvent -- The BindableEvent used for outputting events. -@param Data any -- Configuration data for the BaseCast. -@return BaseCast -- The initialized BaseCast instance. - -To initialize a new BaseCast instance. - -]=] -function BaseCast.Init(BindableOutput: BindableEvent, Data: any) - local self = setmetatable({}, BaseCast) - -- Others - --print(BindableOutput.Parent) - Actor = BindableOutput.Parent - Actives = setmetatable({}, { __mode = "v" }) - -- Bindable - Output = BindableOutput - - local BindableCleaner = Instance.new("BindableEvent") - BindableCleaner.Name = "ActiveCastDestroyer" - BindableCleaner.Parent = Actor - - if Data.useObjectCache then - local BindableObjectCache = Instance.new("BindableFunction") - BindableObjectCache.Parent = Actor - BindableObjectCache.Name = "ActiveCastObjectCache" - ObjectCache = BindableObjectCache - end - - if Data.useBulkMoveTo then - --print("Connecting PreRender") - BulkMoveToConnection = RS.PreRender:ConnectParallel(HandleBulkMoveTo) - end - - ActiveCastCleaner = BindableCleaner - - ActiveCastCleaner.Event:Connect(function(activeCastID: number) - if Actives[activeCastID] then - Actives[activeCastID] = nil - Actor:SetAttribute("Tasks", Actor:GetAttribute("Tasks") - 1) - end - end) - - SyncChanges = Instance.new("BindableEvent") - SyncChanges.Name = "SyncChanges" - SyncChanges.Parent = Actor - - SyncChanges.Event:Connect(function(cast: TypeDef.ActiveCastData) - local ID = cast.ID - local TargetCast = Actives[ID] - - if TargetCast then - for i, v in cast do - TargetCast[i] = v - end - end - - end) - - return self -end - ---[=[ - -@method Raycast -@within BaseCast - -@param Origin Vector3 -- The origin of the raycast. -@param Direction Vector3 -- The direction of the raycast. -@param Velocity Vector3 | number -- The velocity of the raycast. -@param Behavior FastCastBehavior -- The behavior data for the raycast. -@param GUID string -- The unique identifier for the raycast. - -Create a raycast. - -]=] -function BaseCast:Raycast( - Origin: Vector3, - Direction: Vector3, - Velocity: Vector3 | number, - Behavior: TypeDef.FastCastBehavior -) - --table.insert(self.Actives, ActiveCast.new(self, Origin, Direction, Velocity, Behavior)) - Actor:SetAttribute("Tasks", Actor:GetAttribute("Tasks") + 1) - - NextProjectileID += 1 - - Actives[NextProjectileID] = ActiveCast.createCastData({ - Output = Output, - ActiveCastCleaner = ActiveCastCleaner, - ObjectCache = ObjectCache, - SyncChange = SyncChanges - }, NextProjectileID, Origin, Direction, Velocity, Behavior, FastCastEventsModule, { - CastType = EnumCastTypes.Raycast - } :: any) - - if Behavior.FastCastEventsConfig.UseCastFire then - SendCastFire(Actives[NextProjectileID], Origin, Direction, Velocity, Behavior) - end - if Behavior.FastCastEventsModuleConfig.UseCastFire then - if CastFireFunc then - CastFireFunc(Actives[NextProjectileID], Origin, Direction, Velocity, Behavior) - end - end -end - ---[=[ - -@method SetFastCastEventsModule -@within BaseCast - -@param moduleScript ModuleScript -- The FastCastEventsModule to set. - -]=] -function BaseCast:SetFastCastEventsModule(moduleScript: ModuleScript) - FastCastEventsModule = moduleScript - if moduleScript and typeof(moduleScript) == "Instance" and moduleScript:IsA("ModuleScript") then - CastFireFunc = require(moduleScript) - if CastFireFunc.CastFire then - CastFireFunc = CastFireFunc.CastFire - else - CastFireFunc = nil - end - end -end - ---[=[ - -@method Blockcast -@within BaseCast - -@param Origin Vector3 -- The origin of the blockcast. -@param Size Vector3 -- The size of the blockcast. -@param Direction Vector3 -- The direction of the blockcast. -@param Velocity Vector3 | number -- The velocity of the blockcast. -@param Behavior FastCastBehavior -- The behavior data for the blockcast. - -Create a Blockcast. - -]=] -function BaseCast:Blockcast( - Origin: Vector3, - Size: Vector3, - Direction: Vector3, - Velocity: Vector3 | number, - Behavior: TypeDef.FastCastBehavior -) - Actor:SetAttribute("Tasks", Actor:GetAttribute("Tasks") + 1) - NextProjectileID += 1 - - Actives[NextProjectileID] = ActiveCast.createCastData({ - Output = Output, - ActiveCastCleaner = ActiveCastCleaner, - ObjectCache = ObjectCache, - SyncChange = SyncChanges - }, NextProjectileID, Origin, Direction, Velocity, Behavior, FastCastEventsModule, { - CastType = EnumCastTypes.Blockcast, - Size = Size - } :: any) - - if Behavior.FastCastEventsConfig.UseCastFire then - SendCastFire(Actives[NextProjectileID], Origin, Direction, Velocity, Behavior) - end - if Behavior.FastCastEventsModuleConfig.UseCastFire then - if CastFireFunc then - CastFireFunc(Actives[NextProjectileID], Origin, Direction, Velocity, Behavior) - end - end -end - ---[=[ - -@method Spherecast -@within BaseCast - -@param Origin Vector3 -- The origin of the spherecast. -@param Radius number -- The radius of the spherecast. -@param Direction Vector3 -- The direction of the spherecast. -@param Velocity Vector3 | number -- The velocity of the spherecast. -@param Behavior FastCastBehavior -- The behavior data for the spherecast. - -Create a Spherecast. - -]=] -function BaseCast:Spherecast( - Origin: Vector3, - Radius: number, - Direction: Vector3, - Velocity: Vector3 | number, - Behavior: TypeDef.FastCastBehavior -) - Actor:SetAttribute("Tasks", Actor:GetAttribute("Tasks") + 1) - NextProjectileID += 1 - - Actives[NextProjectileID] = ActiveCast.createCastData({ - Output = Output, - ActiveCastCleaner = ActiveCastCleaner, - ObjectCache = ObjectCache, - SyncChange = SyncChanges - }, NextProjectileID, Origin, Direction, Velocity, Behavior, FastCastEventsModule, { - CastType = EnumCastTypes.Spherecast, - Radius = Radius - } :: any) - - if Behavior.FastCastEventsConfig.UseCastFire then - SendCastFire(Actives[NextProjectileID], Origin, Direction, Velocity, Behavior) - end - if Behavior.FastCastEventsModuleConfig.UseCastFire then - if CastFireFunc then - CastFireFunc(Actives[NextProjectileID], Origin, Direction, Velocity, Behavior) - end - end -end - ---[=[ - -@method BindBulkMoveTo -@within BaseCast - -@param bool boolean -- Whether to enable or disable BulkMoveTo. - -Enables or disables the BulkMoveTo feature. - -]=] -function BaseCast:BindBulkMoveTo(bool: boolean) - if bool then - if not BulkMoveToConnection then - BulkMoveToConnection = RS.PreRender:ConnectParallel(HandleBulkMoveTo) - end - else - if BulkMoveToConnection then - BulkMoveToConnection:Disconnect() - BulkMoveToConnection = nil - end - end -end - ---[=[ - -@method BindObjectCache -@within BaseCast - -@param bool boolean -- Whether to enable or disable ObjectCache. - -Enables or disables the ObjectCache feature. - -]=] -function BaseCast:BindObjectCache(bool: boolean) - if bool then - if ObjectCache then - return - end - local BindableObjectCache = Instance.new("BindableFunction") - BindableObjectCache.Parent = Actor - BindableObjectCache.Name = "ActiveCastObjectCache" - ObjectCache = BindableObjectCache - else - if ObjectCache then - ObjectCache:Destroy() - ObjectCache = nil - end - end -end - ---[=[ - -@method Destroy -@within BaseCast - -Destroys the BaseCast instance and cleans up resources. - -]=] -function BaseCast:Destroy() - if BulkMoveToConnection then - BulkMoveToConnection:Disconnect() - BulkMoveToConnection = nil - end - - FastCastEventsModule = nil - - for _, v in Actives do - FastCastM:TerminateCast(v) - end - - Actives = {} - setmetatable(self, nil) -end - -return BaseCast diff --git a/src/FastCast2/FastCastVMs/ClientVM.meta.json b/src/FastCast2/FastCastVMs/ClientVM.meta.json deleted file mode 100644 index 087e903e..00000000 --- a/src/FastCast2/FastCastVMs/ClientVM.meta.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "className": "LocalScript", - "properties": { - "Disabled": true - }, - "children": { - "FastCast2": { - "className": "ObjectValue" - } - } -} diff --git a/src/FastCast2/FastCastVMs/ServerVM.meta.json b/src/FastCast2/FastCastVMs/ServerVM.meta.json deleted file mode 100644 index b2fd39d2..00000000 --- a/src/FastCast2/FastCastVMs/ServerVM.meta.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "className": "Script", - "properties": { - "Disabled": true - }, - "children": { - "FastCast2": { - "className": "ObjectValue" - } - } -} diff --git a/src/FastCast2/Signal.luau b/src/FastCast2/Signal.luau deleted file mode 100644 index 39bda284..00000000 --- a/src/FastCast2/Signal.luau +++ /dev/null @@ -1,261 +0,0 @@ ---!optimize 2 ---!nocheck ---!native - -export type Connection = { - Connected: boolean, - - Disconnect: (self: Connection) -> (), - Reconnect: (self: Connection) -> (), -} - -export type Signal = { - RBXScriptConnection: RBXScriptConnection?, - - Connect: (self: Signal, fn: (...any) -> (), U...) -> Connection, - Once: (self: Signal, fn: (...any) -> (), U...) -> Connection, - Wait: (self: Signal) -> T..., - Fire: (self: Signal, T...) -> (), - DisconnectAll: (self: Signal) -> (), - Destroy: (self: Signal) -> (), -} - -local freeThreads: { thread } = {} - -local function runCallback(callback, thread, ...) - callback(...) - table.insert(freeThreads, thread) -end - -local function yielder() - while true do - runCallback(coroutine.yield()) - end -end - -local Connection = {} -Connection.__index = Connection - -local function disconnect(self: Connection) - if not self.Connected then - return - end - self.Connected = false - - local next = self._next - local prev = self._prev - - if next then - next._prev = prev - end - if prev then - prev._next = next - end - - local signal = self._signal - if signal._head == self then - signal._head = next - end -end - -local function reconnect(self: Connection) - if self.Connected then - return - end - self.Connected = true - - local signal = self._signal - local head = signal._head - if head then - head._prev = self - end - signal._head = self - - self._next = head - self._prev = false -end - -Connection.Disconnect = disconnect -Connection.Reconnect = reconnect - ---\\ Signal //-- -local Signal = {} -Signal.__index = Signal - --- stylua: ignore -local rbxConnect, rbxDisconnect do - if task then - local bindable = Instance.new("BindableEvent") - rbxConnect = bindable.Event.Connect - rbxDisconnect = bindable.Event:Connect(function() end).Disconnect - bindable:Destroy() - end -end - -local function connect(self: Signal, fn: (...any) -> (), ...: U...): Connection - local head = self._head - local varargs = { ... } - local cn = setmetatable({ - Connected = true, - _signal = self, - _fn = fn, - _varargs = if #varargs == 0 then false else varargs, - _next = head, - _prev = false, - }, Connection) - - if head then - head._prev = cn - end - self._head = cn - - return cn -end - -local function once(self: Signal, fn: (...any) -> (), ...: U...) - local cn - cn = connect(self, function(...) - disconnect(cn) - fn(...) - end, ...) - return cn -end - -local wait = if task - then function(self: Signal): ...any - local thread = coroutine.running() - local cn - cn = connect(self, function(...) - disconnect(cn) - if coroutine.status(thread) == "suspended" then - task.spawn(thread, ...) - end - end) - return coroutine.yield() - end - else function(self: Signal): ...any - local thread = coroutine.running() - local cn - cn = connect(self, function(...) - disconnect(cn) - local passed, message = coroutine.resume(thread, ...) - if not passed then - error(message, 0) - end - end) - return coroutine.yield() - end - -local fire = if task - then function(self: Signal, ...: any) - local cn = self._head - while cn do - local thread - if #freeThreads > 0 then - thread = freeThreads[#freeThreads] - freeThreads[#freeThreads] = nil - else - thread = coroutine.create(yielder) - coroutine.resume(thread) - end - - if not cn._varargs then - task.spawn(thread, cn._fn, thread, ...) - else - local args = cn._varargs - local len = #args - local count = len - for _, value in { ... } do - count += 1 - args[count] = value - end - - task.spawn(thread, cn._fn, thread, table.unpack(args)) - - for i = count, len + 1, -1 do - args[i] = nil - end - end - - cn = cn._next - end - end -else function(self: Signal, ...: any) - local cn = self._head - while cn do - local thread - if #freeThreads > 0 then - thread = freeThreads[#freeThreads] - freeThreads[#freeThreads] = nil - else - thread = coroutine.create(yielder) - coroutine.resume(thread) - end - - if not cn._varargs then - local passed, message = coroutine.resume(thread, cn._fn, thread, ...) - if not passed then - print(string.format("%s\nstacktrace:\n%s", message, debug.traceback())) - end - else - local args = cn._varargs - local len = #args - local count = len - for _, value in { ... } do - count += 1 - args[count] = value - end - - local passed, message = coroutine.resume(thread, cn._fn, thread, table.unpack(args)) - if not passed then - print(string.format("%s\nstacktrace:\n%s", message, debug.traceback())) - end - - for i = count, len + 1, -1 do - args[i] = nil - end - end - - cn = cn._next - end - end - - local function disconnectAll(self: Signal) - local cn = self._head - while cn do - disconnect(cn) - cn = cn._next - end - end - - local function destroy(self: Signal) - disconnectAll(self) - local cn = self.RBXScriptConnection - if cn then - rbxDisconnect(cn) - self.RBXScriptConnection = nil - end - end - - --\\ Constructors - function Signal.new(): Signal - return setmetatable({ _head = false }, Signal) - end - - function Signal.wrap(signal: RBXScriptSignal): Signal - local wrapper = setmetatable({ _head = false }, Signal) - wrapper.RBXScriptConnection = rbxConnect(signal, function(...) - fire(wrapper, ...) - end) - return wrapper - end - - --\\ Methods - Signal.Connect = connect - Signal.Once = once - Signal.Wait = wait - Signal.Fire = fire - Signal.DisconnectAll = disconnectAll - Signal.Destroy = destroy - - return { new = Signal.new, wrap = Signal.wrap } diff --git a/src/FastCast2/FastCastEnums.luau b/src/FastCastEnums.luau similarity index 95% rename from src/FastCast2/FastCastEnums.luau rename to src/FastCastEnums.luau index 0fd158c1..6bb04785 100644 --- a/src/FastCast2/FastCastEnums.luau +++ b/src/FastCastEnums.luau @@ -1,7 +1,7 @@ --[[ - Author : Mawin CK - Date : 2025 - -- Verison : 0.0.9 + ]] --!strict diff --git a/src/FastCast2/FastCastVMs/ClientVM.client.luau b/src/FastCastVMs/ClientVM.client.luau similarity index 60% rename from src/FastCast2/FastCastVMs/ClientVM.client.luau rename to src/FastCastVMs/ClientVM.client.luau index e29b0c11..ac7d6d0f 100644 --- a/src/FastCast2/FastCastVMs/ClientVM.client.luau +++ b/src/FastCastVMs/ClientVM.client.luau @@ -5,40 +5,31 @@ -- Modules --- REPLACE WITH ACTUAL PATH (Just use ObjectValue lol) ---local Rep = game:GetService("ReplicatedStorage") ---local FastCast2Module = Rep:WaitForChild("FastCast2") - -local FastCast2Module: ModuleScript = script:WaitForChild("FastCast2").Value :: ModuleScript - - --- Requires -local TypeDefinitions = require(FastCast2Module:WaitForChild("TypeDefinitions")) - -local BaseCast = require(FastCast2Module:WaitForChild("BaseCast")) - -- Variables local actor = script:GetActor() if actor == nil then error("The script must placed inside of actor") end +local BaseCastParallel = nil +local BaseCast = nil + -- Listeners actor:BindToMessage("Init", function(Data: any?) - BaseCast = BaseCast.Init(script.Parent:WaitForChild("Output"), Data) + BaseCastParallel = require(Data.FastCastModule:WaitForChild("BaseCastParallel")) + BaseCast = BaseCastParallel.Init( + script.Parent:WaitForChild("Output"), + Data + ) end) actor:BindToMessage("Raycast", function( origin: Vector3, direction: Vector3, velocity: Vector3 | number, - behavior: TypeDefinitions.FastCastBehavior + behavior: any ) - --print(behavior) - --print(SharedCasters[casterID]) - --StoredCasts[casterID][ID] = ActiveCast.new(bindableEvent, origin, direction, velocity, behavior) - BaseCast:Raycast(origin, direction, velocity, behavior) end) @@ -67,7 +58,7 @@ actor:BindToMessage("Blockcast", function( size: Vector3, direction: Vector3, velocity: Vector3 | number, - behavior: TypeDefinitions.FastCastBehavior + behavior: any ) BaseCast:Blockcast(origin, size, direction, velocity, behavior) end) @@ -77,17 +68,13 @@ actor:BindToMessage("Spherecast", function( radius : number, direction : Vector3, velocity : Vector3 | number, - behavior : TypeDefinitions.FastCastBehavior + behavior :any ) BaseCast:Spherecast(origin, radius, direction, velocity, behavior) end) -actor:BindToMessage("BindBulkMoveTo", function(bool: boolean) - BaseCast:BindBulkMoveTo(bool) -end) - -actor:BindToMessage("BindObjectCache", function(bool: boolean) - BaseCast:BindObjectCache(bool) +actor:BindToMessage("SetMovementModeEnabled", function(enabled: boolean, mode: "BulkMoveTo" | "Motor6D") + BaseCast:SetMovementModeEnabled(enabled, mode) end) -- CleanUp diff --git a/src/FastCastVMs/ClientVM.meta.json b/src/FastCastVMs/ClientVM.meta.json new file mode 100644 index 00000000..80d45a0a --- /dev/null +++ b/src/FastCastVMs/ClientVM.meta.json @@ -0,0 +1,6 @@ +{ + "className": "LocalScript", + "properties": { + "Disabled": true + } +} diff --git a/src/FastCastVMs/ServerVM.meta.json b/src/FastCastVMs/ServerVM.meta.json new file mode 100644 index 00000000..d94113d1 --- /dev/null +++ b/src/FastCastVMs/ServerVM.meta.json @@ -0,0 +1,6 @@ +{ + "className": "Script", + "properties": { + "Disabled": true + } +} diff --git a/src/FastCast2/FastCastVMs/ServerVM.server.luau b/src/FastCastVMs/ServerVM.server.luau similarity index 70% rename from src/FastCast2/FastCastVMs/ServerVM.server.luau rename to src/FastCastVMs/ServerVM.server.luau index 2c96a6f5..f1613b9e 100644 --- a/src/FastCast2/FastCastVMs/ServerVM.server.luau +++ b/src/FastCastVMs/ServerVM.server.luau @@ -5,27 +5,20 @@ -- Modules --- REPLACE WITH ACTUAL PATH (Just use ObjectValue lol) ---local Rep = game:GetService("ReplicatedStorage") ---local FastCast2Module = Rep:WaitForChild("FastCast2") - -local FastCast2Module: ModuleScript = script:WaitForChild("FastCast2").Value :: ModuleScript - - -local TypeDefinitions = require(FastCast2Module:WaitForChild("TypeDefinitions")) - -local BaseCast = require(FastCast2Module:WaitForChild("BaseCast")) - -- Variables local actor = script:GetActor() if actor == nil then error("The script must placed inside of actor") end +local BaseCastParallel = nil +local BaseCast = nil + -- Listeners actor:BindToMessage("Init", function(Data : any?) - BaseCast = BaseCast.Init( + BaseCastParallel = require(Data.FastCastModule:WaitForChild("BaseCastParallel")) + BaseCast = BaseCastParallel.Init( script.Parent:WaitForChild("Output"), Data ) @@ -35,7 +28,7 @@ actor:BindToMessage("Raycast", function( origin : Vector3, direction : Vector3, velocity : Vector3 | number, - behavior : TypeDefinitions.FastCastBehavior + behavior : any ) --print(behavior) --print(SharedCasters[casterID]) @@ -69,7 +62,7 @@ actor:BindToMessage("Blockcast", function( size : Vector3, direction : Vector3, velocity : Vector3 | number, - behavior : TypeDefinitions.FastCastBehavior + behavior : any ) --print(behavior) --print(SharedCasters[casterID]) @@ -83,17 +76,13 @@ actor:BindToMessage("Spherecast", function( radius : number, direction : Vector3, velocity : Vector3 | number, - behavior : TypeDefinitions.FastCastBehavior + behavior : any ) BaseCast:Spherecast(origin, radius, direction, velocity, behavior) end) -actor:BindToMessage("BindBulkMoveTo", function(bool : boolean) - BaseCast:BindBulkMoveTo(bool) -end) - -actor:BindToMessage("BindObjectCache", function(bool : boolean) - BaseCast:BindObjectCache(bool) +actor:BindToMessage("SetMovementModeEnabled", function(enabled: boolean, mode: "BulkMoveTo" | "Motor6D") + BaseCast:SetMovementModeEnabled(enabled, mode) end) -- CleanUp diff --git a/src/FastCast2/FastCastVMs/init.luau b/src/FastCastVMs/init.luau similarity index 93% rename from src/FastCast2/FastCastVMs/init.luau rename to src/FastCastVMs/init.luau index a9a84fb5..d729b7c8 100644 --- a/src/FastCast2/FastCastVMs/init.luau +++ b/src/FastCastVMs/init.luau @@ -137,12 +137,13 @@ function Dispatcher.new(Threads: number, Data : any?, Callback: (...any) -> ()?) assert(type(Threads) == "number" and Threads > 0, "Invalid argument #2 to 'Dispatcher.new', threads must be a positive integer.") if not AlreadyInit then - error("Please Init dispatcher, RunContext : " .. IS_SERVER and "Server"or "Client") + error("Please Init dispatcher, RunContext : " .. (IS_SERVER and "Server" or "Client")) end local self: Dispatcher = setmetatable({ - Threads = {} + Threads = {}, + _nextIndex = 0 } :: any, Dispatcher) --> Allocate initial threads @@ -162,7 +163,7 @@ function Dispatcher:Allocate(Threads: number, Data: any?, Callback: (...any) -> Actor.Parent = Container local controller = Actor:FindFirstChild(ControllerName) - + if Callback then local Output = Instance.new("BindableEvent") Output.Name = "Output" @@ -170,7 +171,7 @@ function Dispatcher:Allocate(Threads: number, Data: any?, Callback: (...any) -> Actor.Output.Event:Connect(Callback) end - + if controller then controller.Enabled = true end @@ -205,13 +206,8 @@ end

]] function Dispatcher:Dispatch(Message : string?, ...) - local Threads: {Actor} = table.clone(self.Threads) - table.sort(Threads, function(a: Actor, b: Actor) - local aTasks = a:GetAttribute("Tasks") or 0 - local bTasks = b:GetAttribute("Tasks") or 0 - return aTasks < bTasks - end) - Threads[1]:SendMessage(Message or "Dispatch", ...) + self._nextIndex = self._nextIndex % #self.Threads + 1 + self.Threads[self._nextIndex]:SendMessage(Message or "Dispatch", ...) end function Dispatcher:Destroy(destroySource: boolean) diff --git a/src/Motor6DCache.luau b/src/Motor6DCache.luau new file mode 100644 index 00000000..cc090cfb --- /dev/null +++ b/src/Motor6DCache.luau @@ -0,0 +1,104 @@ +--[[ + - Author : Mawin CK + - Date : 2026 + + + Motor6D Pool for efficient projectile movement using Transform mode. + NOTE: + I'm sorry for stealing some code bro shoutout to DrSinek, + I just wanted to make it more efficient and I didn't want to rewrite the whole thing +]] + +-- Services +local HTTPS = game:GetService("HttpService") + +local GROWTH_RATE = 2 +local INITIAL_POOL_SIZE = 128 + +local Motor6DCache = {} +Motor6DCache.__index = Motor6DCache +Motor6DCache.__type = "Motor6DCache" + +function Motor6DCache.new() + local self = setmetatable({}, Motor6DCache) + + -- Folder + local Motor6DFolder = Instance.new("Folder") + Motor6DFolder.Parent = workspace + Motor6DFolder.Name = "Motor6D" .. tostring(HTTPS:GenerateGUID()) + + -- Motor6DAnchor + local Motor6DAnchor: BasePart = Instance.new("Part") + Motor6DAnchor.Name = "FastCastMotor6DAnchor" + Motor6DAnchor.Transparency = 1 + Motor6DAnchor.CanCollide = false + Motor6DAnchor.CanQuery = false + Motor6DAnchor.CanTouch = false + Motor6DAnchor.Anchored = true + Motor6DAnchor.CFrame = CFrame.identity + Motor6DAnchor.Parent = Motor6DFolder + + self.Motor6DFolder = Motor6DFolder + self.Motor6DAnchor = Motor6DAnchor + self.FreeMotor6Ds = {} + self.PoolSize = 0 + + self:GrowPool(INITIAL_POOL_SIZE) + return self +end + +function Motor6DCache:GrowPool(target: number) + local growth = target - self.PoolSize + for _ = 1, growth do + local motor6d = Instance.new("Motor6D") + motor6d.Name = "FastCastMotor6D" + table.insert(self.FreeMotor6Ds, motor6d) + end + self.PoolSize = target +end + +function Motor6DCache:Get(): Motor6D + if #self.FreeMotor6Ds == 0 then + self:GrowPool(self.PoolSize * GROWTH_RATE) + end + return table.remove(self.FreeMotor6Ds) :: Motor6D +end + +function Motor6DCache:Return(motor6d: Motor6D) + motor6d.Part0 = nil + motor6d.Part1 = nil + motor6d.Parent = nil + motor6d.Transform = CFrame.identity + table.insert(self.FreeMotor6Ds, motor6d) +end + +function Motor6DCache:Connect(projectilePart: BasePart?): Motor6D? + if not projectilePart then return nil end + + projectilePart.Anchored = false + + local motor6d = self:Get() + motor6d.Transform = projectilePart.CFrame + motor6d.Part0 = self.Motor6DAnchor + motor6d.Part1 = projectilePart + motor6d.Parent = self.Motor6DAnchor + + return motor6d +end + +function Motor6DCache:Disconnect(motor6d: Motor6D?) + if motor6d then + self:Return(motor6d) + end +end + +function Motor6DCache:Destroy() + for _, motor6d in self.FreeMotor6Ds do + motor6d:Destroy() + end + self.Motor6DFolder:Destroy() + self.FreeMotor6Ds = {} + self.PoolSize = 0 +end + +return Motor6DCache \ No newline at end of file diff --git a/src/FastCast2/ObjectCache.luau b/src/ObjectCache.luau similarity index 87% rename from src/FastCast2/ObjectCache.luau rename to src/ObjectCache.luau index 973f31a8..2415e5ff 100644 --- a/src/FastCast2/ObjectCache.luau +++ b/src/ObjectCache.luau @@ -61,7 +61,7 @@ function Cache:_GetNew(Amount: number, Warn: boolean) local Object = Template:Clone() local ObjectRoot: BasePart = if IsTemplateModel then (Object:: Model).PrimaryPart:: BasePart else Object:: BasePart - FreeObjectsContainer[Index] = ObjectRoot + FreeObjectsContainer[Index] = Object local OffsetIndex = Index - InitialLength TargetParts[OffsetIndex] = ObjectRoot @@ -79,9 +79,10 @@ function Cache:_GetNew(Amount: number, Warn: boolean) end function Cache:GetPart(PartCFrame: CFrame?): BasePart - local Part = table.remove(self._FreeObjects) or self:_GetNew(self._ExpandAmount, true) + local Object = table.remove(self._FreeObjects) or self:_GetNew(self._ExpandAmount, true) + local Part = if self._IsTemplateModel then (Object :: Model).PrimaryPart :: BasePart else Object :: BasePart - --local ID = HTTPS:GenerateGUID(false) + self._PartToObject[Part] = Object self._Objects[Part] = nil if PartCFrame then table.insert(MovingParts, Part) @@ -92,21 +93,24 @@ function Cache:GetPart(PartCFrame: CFrame?): BasePart task.defer(UpdateMovementThread) end end - - --Part:SetAttribute("ID", ID) - --print("GET " .. ID) + return Part end function Cache:ReturnPart(Part: BasePart) - --print("RET " .. Part:GetAttribute("ID")) if self._Objects[Part] then return end - --print("RETURNED") self._Objects[Part] = true - table.insert(self._FreeObjects, Part) + local Object = self._PartToObject[Part] + if Object then + self._PartToObject[Part] = nil + else + Object = Part + end + + table.insert(self._FreeObjects, Object) table.insert(MovingParts, Part) table.insert(MovingCFrames, FAR_AWAY_CFRAME) @@ -121,11 +125,11 @@ function Cache:Update() end function Cache:ExpandCache(Amount: number) - assert(typeof(Amount) ~= "number" or Amount >= 0, `Invalid argument #1 to 'ObjectCache:ExpandCache' (positive number expected, got {typeof(Amount)})`) + assert(typeof(Amount) == "number" and Amount >= 0, `Invalid argument #1 to 'ObjectCache:ExpandCache' (positive number expected, got {typeof(Amount)})`) self:_GetNew(Amount, false) end function Cache:SetExpandAmount(Amount: number) - assert(typeof(Amount) ~= "number" or Amount > 0, `Invalid argument #1 to 'ObjectCache:SetExpandAmount' (positive number expected, got {typeof(Amount)})`) + assert(typeof(Amount) == "number" and Amount > 0, `Invalid argument #1 to 'ObjectCache:SetExpandAmount' (positive number expected, got {typeof(Amount)})`) self._ExpandAmount = Amount end @@ -188,8 +192,9 @@ function Constructor.new(Template: BasePart | Model, CacheSize: number?, CachesC CacheHolder = CacheParent, _ExpandAmount = EXPAND_BY_AMOUNT, _Template = Template, - _FreeObjects = TargetParts, + _FreeObjects = FreeObjects, _Objects = Objects, + _PartToObject = {}, _IsTemplateModel = IsTemplateModel, _PreallocatedAmount = PreallocAmount, Type = "ObjectCache" diff --git a/src/ParallelSimulation.luau b/src/ParallelSimulation.luau new file mode 100644 index 00000000..ab7e42cd --- /dev/null +++ b/src/ParallelSimulation.luau @@ -0,0 +1,936 @@ +--[[ + - Author: Mawin CK + - Date: 2026 +]] + +-- Services + +local RS = game:GetService("RunService") + +local FastCastModule = script.Parent + +-- Requires + +local TypeDef = require(FastCastModule:WaitForChild("TypeDefinitions")) +local FastCastEnums = require(FastCastModule:WaitForChild("FastCastEnums")) +local Config = require(script.Parent:WaitForChild("Config")) +local DebugLogging = Config.DebugLogging + + +-- Constants +local EnumCastTypes = FastCastEnums.CastType +local DEFAULT_MAX_DISTANCE = 1000 +local FC_VIS_OBJ_NAME = "FastCastVisualizationObjects" + +-- Variables + +local casts_TotalRunTime = {} :: { [number]: number } +local casts_DistanceCovered = {} :: { [number]: number } +local casts_HighFidelitySegmentSize = {} :: { [number]: number } +local casts_HighFidelityBehavior = {} :: { [number]: number } +local casts_IsActivelySimulatingPierce = {} :: { [number]: boolean } +local casts_IsActivelyResimulating = {} :: { [number]: boolean } +local casts_CancelHighResCast = {} :: { [number]: boolean } +local casts_Trajectory = {} :: { [number]: TypeDef.CastTrajectory } +local casts_FastCastEventsModuleConfig = {} :: { [number]: TypeDef.FastCastEventsModuleConfig } +local casts_FastCastEventsConfig = {} :: { [number]: TypeDef.FastCastEventsConfig } +local casts_VisualizeCasts = {} :: { [number]: boolean } +local casts_VisualizeCastSettings = {} :: { [number]: TypeDef.VisualizeCastSettings } +local casts_RayInfo = {} :: { [number]: TypeDef.CastRayInfo } +local casts_UserData = {} :: { [number]: any } +local casts_CFrame = {} :: { [number]: CFrame } +local casts_CastType = {} :: { [number]: number } +local casts_CastVariant = {} :: { [number]: CastVariants } +local casts_Origin = {} :: { [number]: Vector3 } +local casts_Acceleration = {} :: { [number]: Vector3 } +local casts_MaxDistance = {} :: { [number]: number } +local casts_ActiveMotor6Ds = {} :: { [number]: Motor6D } +local casts_RayDisplacement = {} :: { [number]: number } + +local casts_ID = {} :: { number } +local casts_ID_Index = {} :: { [number]: number } + +local casts_FastCastEvents = {} :: { [number]: ModuleScript } + +-- Types + +type QueuedEventData = { + eventType: string, + args: { any } +} + +type QueuedVisualizeCastData = { + castStartCFrame: CFrame, + sub: boolean +} + +type QueuedVisualizeHitData = { + atCF: CFrame, + wasPierce: boolean, + sub: boolean +} + +type BlockcastVariant = { CastType: number, Size: Vector3 } +type SpherecastVariant = { CastType: number, Radius: number } +type CastVariants = BlockcastVariant | SpherecastVariant + +type RayVisualizerVariant = { castLength: number } +type BlockVisualizerVariant = { size: Vector3 } +type SphereVisualizerVariant = { radius: number } +type CastVisualizerVariants = RayVisualizerVariant | BlockVisualizerVariant | SphereVisualizerVariant + +-- queued things + +local queuedEvents: { [number]: { QueuedEventData } } = {} +local queuedVisualizes: { [number]: { QueuedVisualizeCastData | QueuedVisualizeHitData } } = {} + +local ActivesRef: any = nil +local BaseCastRef = nil :: any +local CurrentMovementMode: "BulkMoveTo" | "Motor6D" = "BulkMoveTo" +local MovementEnabled = false + +local castHandlers = { + [EnumCastTypes.Raycast] = function( + targetWorldRoot: WorldRoot, + origin: Vector3, + direction: Vector3, + params: RaycastParams + ) + return targetWorldRoot:Raycast(origin, direction, params) + end, + [EnumCastTypes.Blockcast] = function( + targetWorldRoot: WorldRoot, + origin: Vector3, + direction: Vector3, + params: RaycastParams, + variant: BlockcastVariant + ) + return targetWorldRoot:Blockcast(CFrame.new(origin), variant.Size, direction, params) + end, + [EnumCastTypes.Spherecast] = function( + targetWorldRoot: WorldRoot, + origin: Vector3, + direction: Vector3, + params: RaycastParams, + variant: SpherecastVariant + ) + return targetWorldRoot:Spherecast(origin, variant.Radius, direction, params) + end +} + +-- Utils + +local function GetPositionAtTime( + t: number, + origin: Vector3, + initialVelocity: Vector3, + acceleration: Vector3 +): Vector3 + local force = Vector3.new( + (acceleration.X * t ^ 2) / 2, + (acceleration.Y * t ^ 2) / 2, + (acceleration.Z * t ^ 2) / 2 + ) + return origin + (initialVelocity * t) + force +end + +local function GetVelocityAtTime(time: number, initialVelocity: Vector3, acceleration: Vector3): Vector3 + return initialVelocity + acceleration * time +end + +local function TerminateCast(cast: any, castTerminatingFunction: TypeDef.OnCastTerminatingFunction?) + local FastCastEventsConfig = cast.StateInfo.FastCastEventsConfig + if FastCastEventsConfig and FastCastEventsConfig.UseCastTerminating then + cast.Caster.Output:Fire("CastTerminating", cast) + end + + if castTerminatingFunction then + castTerminatingFunction((cast :: any)) + end + + cast.Caster.ActiveCastCleaner:Fire(cast.ID) + + for key, _ in (cast :: any) do + cast[key] = nil + end +end + +local function DebrisAdd(obj: Instance, Lifetime: number) + if not obj then + return + end + if Lifetime <= 0 then + obj:Destroy() + return + end + + task.delay(Lifetime, function() + obj:Destroy() + end) +end + +local function GetFastCastVisualizationContainer(): Instance + local fcVisualizationObjects = workspace.Terrain:FindFirstChild(FC_VIS_OBJ_NAME) + if fcVisualizationObjects then + return fcVisualizationObjects + end + + fcVisualizationObjects = Instance.new("Folder") + fcVisualizationObjects.Name = FC_VIS_OBJ_NAME + fcVisualizationObjects.Archivable = false + fcVisualizationObjects.Parent = workspace.Terrain + return fcVisualizationObjects +end + +local function DbgVisualizeRaySegment( + castStartCFrame: CFrame, + VisualizeCastSettings: TypeDef.VisualizeCastSettings, + variant: RayVisualizerVariant +): ConeHandleAdornment? + local adornment = Instance.new("ConeHandleAdornment") + adornment.Adornee = workspace.Terrain + adornment.CFrame = castStartCFrame + adornment.Height = variant.castLength + adornment.Color3 = VisualizeCastSettings.Debug_SegmentColor + adornment.Radius = VisualizeCastSettings.Debug_SegmentSize + adornment.Transparency = VisualizeCastSettings.Debug_SegmentTransparency + adornment.Parent = GetFastCastVisualizationContainer() + + DebrisAdd(adornment, VisualizeCastSettings.Debug_RayLifetime) + return adornment +end + +local function DbgVisualizeBlockSegment( + castStartCFrame: CFrame, + VisualizeCastSetting: TypeDef.VisualizeCastSettings, + variant: BlockVisualizerVariant +): BoxHandleAdornment? + local adornment = Instance.new("BoxHandleAdornment") + adornment.Adornee = workspace.Terrain + adornment.CFrame = castStartCFrame + --adornment.Height = castLength + + adornment.Size = variant.size + adornment.Color3 = VisualizeCastSetting.Debug_SegmentColor + adornment.Transparency = VisualizeCastSetting.Debug_SegmentTransparency + + adornment.Parent = GetFastCastVisualizationContainer() + + DebrisAdd(adornment, VisualizeCastSetting.Debug_RayLifetime) + return adornment +end + +local function DbgVisualizeSphereSegment( + castStartCFrame: CFrame, + VisualizeCastSetting: TypeDef.VisualizeCastSettings, + variant: SphereVisualizerVariant +): SphereHandleAdornment? + local adornment = Instance.new("SphereHandleAdornment") + adornment.Adornee = workspace.Terrain + adornment.CFrame = castStartCFrame + --adornment.Height = castLength + adornment.Radius = variant.radius + --adornment.Size = Vector3.new(size.X, size.Y, size.Z + castLength) + adornment.Color3 = VisualizeCastSetting.Debug_SegmentColor + adornment.Transparency = VisualizeCastSetting.Debug_SegmentTransparency + + adornment.Parent = GetFastCastVisualizationContainer() + + DebrisAdd(adornment, VisualizeCastSetting.Debug_RayLifetime) + return adornment +end + +local function DbgVisualizeHit( + atCF: CFrame, + wasPierce: boolean, + VisualizeCastSettings: TypeDef.VisualizeCastSettings +): SphereHandleAdornment? + local adornment = Instance.new("SphereHandleAdornment") + adornment.Adornee = workspace.Terrain + adornment.CFrame = atCF + -- Alert! someone is Mawining it!!!!! + adornment.Radius = (wasPierce == false) and VisualizeCastSettings.Debug_HitSize + or VisualizeCastSettings.Debug_RayPierceSize + adornment.Transparency = (wasPierce == false) and VisualizeCastSettings.Debug_HitTransparency + or VisualizeCastSettings.Debug_RayPierceTransparency + adornment.Color3 = (wasPierce == false) and VisualizeCastSettings.Debug_HitColor + or VisualizeCastSettings.Debug_RayPierceColor + adornment.Parent = GetFastCastVisualizationContainer() + + DebrisAdd(adornment, VisualizeCastSettings.Debug_HitLifetime) + return adornment +end + +local Visualizers = { + [EnumCastTypes.Raycast] = DbgVisualizeRaySegment, + [EnumCastTypes.Blockcast] = DbgVisualizeBlockSegment, + [EnumCastTypes.Spherecast] = DbgVisualizeSphereSegment +} + +local function QueueVisualize(castID: number, visualizeData: QueuedVisualizeCastData | QueuedVisualizeHitData) + if not casts_VisualizeCasts[castID] then + return + end + + if not queuedVisualizes[castID] then + queuedVisualizes[castID] = {} + end + + table.insert(queuedVisualizes[castID], visualizeData) +end + +local function FireQueuedVisualizes(unFiredVisualizes: { [number]: { QueuedVisualizeCastData | QueuedVisualizeHitData }}) + local sortedIDs = {} + for id in unFiredVisualizes do + table.insert(sortedIDs, id) + end + table.sort(sortedIDs) + + for _, castID in sortedIDs do + local visualizesList = unFiredVisualizes[castID] + if not visualizesList or not next(visualizesList) then + continue + end + + for _, visualizeData in visualizesList do + -- VisualizeHit + if visualizeData.atCF then + if DebugLogging.Hit and not visualizeData.wasPierce and not visualizeData.sub then + print("Hit was successful. Terminating.") + end + if DebugLogging.Pierced and visualizeData.wasPierce and not visualizeData.sub then + print("Piercing function returned TRUE to pierce this part.") + end + DbgVisualizeHit(visualizeData.atCF, visualizeData.wasPierce, casts_VisualizeCastSettings[castID]) + continue + end + -- VisualizeCasts + if DebugLogging.Segment and visualizeData.sub then + print("Subcast visualization") + end + local castType = casts_CastType[castID] + local castVisualizer = Visualizers[castType] + if not castVisualizer then + continue + end + local visualizerVariant: CastVisualizerVariants + if castType == EnumCastTypes.Raycast then + visualizerVariant = { castLength = casts_RayDisplacement[castID] or 1 } + elseif castType == EnumCastTypes.Blockcast then + visualizerVariant = { size = (casts_CastVariant[castID] :: BlockcastVariant).Size } + else + visualizerVariant = { radius = (casts_CastVariant[castID] :: SpherecastVariant).Radius } + end + castVisualizer(visualizeData.castStartCFrame, casts_VisualizeCastSettings[castID], visualizerVariant) + end + end +end + +local function QueueEvent(castID: number, eventType: string, ...: any) + local args = { ... } + if not queuedEvents[castID] then + queuedEvents[castID] = {} + end + table.insert(queuedEvents[castID], { + eventType = eventType, + args = args + }) +end + +local function FireQueuedEvents(events: { [number]: { QueuedEventData } }) + local sortedIDs = {} + for id in events do + table.insert(sortedIDs, id) + end + table.sort(sortedIDs) + + for _, castID in sortedIDs do + local eventList = events[castID] + if not eventList or not next(eventList) then + continue + end + + for _, event in eventList do + local cast = ActivesRef[castID] + if not cast then + continue + end + + local eventType: string = event.eventType + local args: { any } = event.args + + local caster = cast.Caster + local moduleConfig = casts_FastCastEventsModuleConfig[castID] + local eventConfig = casts_FastCastEventsConfig[castID] + local fastCastEvents = casts_FastCastEvents[castID] + + if eventType == "LengthChanged" then + local lastPoint = args[1] + local rayDir = args[2] + local rayDisplacement = args[3] + + if eventConfig and eventConfig.UseLengthChanged then + caster.Output:Fire("LengthChanged", cast, lastPoint, rayDir, rayDisplacement) + end + + if moduleConfig and moduleConfig.UseLengthChanged and fastCastEvents and fastCastEvents.LengthChanged then + fastCastEvents.LengthChanged(cast, lastPoint, rayDir, rayDisplacement) + end + + elseif eventType == "Hit" then + local result = args[1] + local velocity = args[2] + local cosmeticBulletObject = args[3] + + if eventConfig and eventConfig.UseHit then + caster.Output:Fire("Hit", cast, result, velocity, cosmeticBulletObject) + end + + if moduleConfig and moduleConfig.UseHit and fastCastEvents and fastCastEvents.Hit then + fastCastEvents.Hit(cast, result, velocity, cosmeticBulletObject) + end + + elseif eventType == "Pierced" then + local result = args[1] + local velocity = args[2] + local cosmeticBulletObject = args[3] + + if eventConfig and eventConfig.UsePierced then + caster.Output:Fire("Pierced", cast, result, velocity, cosmeticBulletObject) + end + + if moduleConfig and moduleConfig.UsePierced and fastCastEvents and fastCastEvents.Pierced then + fastCastEvents.Pierced(cast, result, velocity, cosmeticBulletObject) + end + + elseif eventType == "CastTerminating" then + local castTerminatingfn = args[1] + TerminateCast(cast, castTerminatingfn) + end + end + end +end + +local function BulkMoveTo() + if CurrentMovementMode ~= "BulkMoveTo" or not MovementEnabled then + return + end + + local parts = table.create(#casts_ID) + local cframes = table.create(#casts_ID) + + for _, id in casts_ID do + local cosmeticPart = casts_RayInfo[id] and casts_RayInfo[id].CosmeticBulletObject + if cosmeticPart and cosmeticPart.Parent then + table.insert(parts, cosmeticPart) + table.insert(cframes, casts_CFrame[id]) + end + end + + if #parts > 0 then + workspace:BulkMoveTo(parts, cframes, Enum.BulkMoveMode.FireCFrameChanged) + end +end + +local function UpdateMotor6Ds() + if CurrentMovementMode ~= "Motor6D" or not MovementEnabled then + return + end + + for id, motor6d in casts_ActiveMotor6Ds do + if motor6d and motor6d.Parent then + motor6d.Transform = casts_CFrame[id] + end + end +end + +-- ParallelSimulation + +local ParallelSimulation = {} +ParallelSimulation.Connection = nil :: RBXScriptConnection? + +function ParallelSimulation.Init(baseCastRef: any) + BaseCastRef = baseCastRef + ActivesRef = baseCastRef.Actives +end + +function ParallelSimulation.Register(cast: any) + local id = cast.ID + + casts_TotalRunTime[id] = cast.StateInfo.TotalRuntime or 0 + casts_DistanceCovered[id] = 0 + casts_HighFidelitySegmentSize[id] = cast.StateInfo.HighFidelitySegmentSize or 0.1 + casts_HighFidelityBehavior[id] = cast.StateInfo.HighFidelityBehavior or 0 + casts_IsActivelySimulatingPierce[id] = false + casts_IsActivelyResimulating[id] = false + casts_CancelHighResCast[id] = false + casts_Trajectory[id] = cast.StateInfo.Trajectory + casts_FastCastEventsModuleConfig[id] = cast.StateInfo.FastCastEventsModuleConfig + casts_FastCastEventsConfig[id] = cast.StateInfo.FastCastEventsConfig + casts_VisualizeCasts[id] = cast.StateInfo.VisualizeCasts + casts_VisualizeCastSettings[id] = cast.StateInfo.VisualizeCastSettings + casts_RayInfo[id] = cast.RayInfo + casts_UserData[id] = cast.UserData + casts_CastType[id] = cast.CastVariant.CastType + casts_CastVariant[id] = cast.CastVariant + casts_Origin[id] = cast.StateInfo.Trajectory.Origin + casts_Acceleration[id] = cast.StateInfo.Trajectory.Acceleration + casts_MaxDistance[id] = cast.RayInfo.MaxDistance or DEFAULT_MAX_DISTANCE + table.insert(casts_ID, id) + casts_ID_Index[id] = #casts_ID + + local eventModule = cast.RayInfo.FastCastEventsModule + casts_FastCastEvents[id] = (typeof(eventModule) == "ModuleScript" or (typeof(eventModule) == "Instance" and eventModule:IsA("ModuleScript"))) and require(eventModule) or nil + + local position = GetPositionAtTime( + casts_TotalRunTime[id], + casts_Trajectory[id].Origin, + casts_Trajectory[id].InitialVelocity, + casts_Trajectory[id].Acceleration + ) + casts_CFrame[id] = CFrame.new(position) + + cast.CFrame = casts_CFrame[id] + + if CurrentMovementMode == "Motor6D" and MovementEnabled then + local cosmeticPart = casts_RayInfo[id] and casts_RayInfo[id].CosmeticBulletObject + if cosmeticPart and BaseCastRef and BaseCastRef._GetMotor6D then + local motor6d = BaseCastRef:_GetMotor6D(cosmeticPart) + casts_ActiveMotor6Ds[id] = motor6d + end + end + + queuedEvents[id] = {} +end + +function ParallelSimulation.Unregister(castID: number) + casts_TotalRunTime[castID] = nil + casts_DistanceCovered[castID] = nil + casts_HighFidelitySegmentSize[castID] = nil + casts_HighFidelityBehavior[castID] = nil + casts_IsActivelySimulatingPierce[castID] = nil + casts_IsActivelyResimulating[castID] = nil + casts_CancelHighResCast[castID] = nil + casts_Trajectory[castID] = nil + casts_FastCastEventsModuleConfig[castID] = nil + casts_FastCastEventsConfig[castID] = nil + casts_VisualizeCasts[castID] = nil + casts_VisualizeCastSettings[castID] = nil + casts_RayInfo[castID] = nil + casts_UserData[castID] = nil + casts_CFrame[castID] = nil + casts_CastType[castID] = nil + casts_CastVariant[castID] = nil + casts_Origin[castID] = nil + casts_Acceleration[castID] = nil + casts_MaxDistance[castID] = nil + casts_RayDisplacement[castID] = nil + + if casts_ActiveMotor6Ds[castID] then + if BaseCastRef and BaseCastRef._ReturnMotor6D then + BaseCastRef:_ReturnMotor6D(casts_ActiveMotor6Ds[castID]) + end + casts_ActiveMotor6Ds[castID] = nil + end + + local idx = casts_ID_Index[castID] + if idx then + local lastID = casts_ID[#casts_ID] + casts_ID[idx] = lastID + casts_ID_Index[lastID] = idx + casts_ID[#casts_ID] = nil + casts_ID_Index[castID] = nil + end + casts_FastCastEvents[castID] = nil + + queuedEvents[castID] = nil + queuedVisualizes[castID] = nil +end + +function ParallelSimulation.SetMovementModeEnabled(enabled: boolean, mode: "BulkMoveTo" | "Motor6D") + local oldMode = CurrentMovementMode + CurrentMovementMode = mode + MovementEnabled = enabled + + if oldMode == "Motor6D" and mode ~= "Motor6D" then + for id, motor6d in casts_ActiveMotor6Ds do + if BaseCastRef and BaseCastRef._ReturnMotor6D then + BaseCastRef:_ReturnMotor6D(motor6d) + end + casts_ActiveMotor6Ds[id] = nil + end + end + + if mode == "Motor6D" and enabled and oldMode ~= "Motor6D" then + for _, id in casts_ID do + if not casts_ActiveMotor6Ds[id] then + local cosmeticPart = casts_RayInfo[id] and casts_RayInfo[id].CosmeticBulletObject + if cosmeticPart and BaseCastRef and BaseCastRef._GetMotor6D then + local motor6d = BaseCastRef:_GetMotor6D(cosmeticPart) + casts_ActiveMotor6Ds[id] = motor6d + end + end + end + end +end + +-- RS +local function SimulateCast( + id: number, + delta: number, + FastCastEvents +) + if DebugLogging.Casting then + print("Casting for frame - SimulateCast") + end + + local trajectory = casts_Trajectory[id] + + local origin = trajectory.Origin + local totalDelta = casts_TotalRunTime[id] - trajectory.StartTime + local initialVelocity = trajectory.InitialVelocity + local acceleration = trajectory.Acceleration + + local lastPoint = GetPositionAtTime(totalDelta, origin, initialVelocity, acceleration) + + local lastDelta = casts_TotalRunTime[id] - trajectory.StartTime + + casts_TotalRunTime[id] += delta + + totalDelta = casts_TotalRunTime[id] - trajectory.StartTime + + local currentTarget = GetPositionAtTime(totalDelta, origin, initialVelocity, acceleration) + local segmentVelocity = GetVelocityAtTime(totalDelta, initialVelocity, acceleration) + local totalDisplacement = currentTarget - lastPoint + + local rayDir = totalDisplacement + + local targetWorldRoot = casts_RayInfo[id].WorldRoot + + local castHandler = castHandlers[casts_CastVariant[id].CastType] + local resultOfCast = castHandler(targetWorldRoot, lastPoint, rayDir, casts_RayInfo[id].Parameters, casts_CastVariant[id]) + + local point = currentTarget + local part: Instance? = nil + + if resultOfCast ~= nil then + point = resultOfCast.Position + part = resultOfCast.Instance + end + + local rayDisplacement = (point - lastPoint).Magnitude + casts_RayDisplacement[id] = rayDisplacement + + casts_CFrame[id] = (if rayDir ~= Vector3.new() then CFrame.new(lastPoint, lastPoint + rayDir) else CFrame.new(lastPoint)) * CFrame.new(0, 0, -rayDisplacement / 2) + + QueueEvent(id, "LengthChanged", lastPoint, rayDir.Unit, rayDisplacement) + + casts_DistanceCovered[id] += rayDisplacement + + if delta > 0 then + QueueVisualize( + id, + { + castStartCFrame = CFrame.new(lastPoint, lastPoint+rayDir), + sub = false + } :: any + ) + end + + local canPierceCheckfn: TypeDef.CanPierceFunction? = nil + local castTerminatingfn: TypeDef.OnCastTerminatingFunction? = nil + + if FastCastEvents then + canPierceCheckfn = casts_FastCastEventsModuleConfig[id].UseCanPierce and FastCastEvents.CanPierce or nil + castTerminatingfn = casts_FastCastEventsModuleConfig[id].UseCastTerminating and FastCastEvents.CastTerminating or nil + end + + -- NOTE: Please dont remove "part and" + -- Why? basically when part doesn't exist it will do nothing, but removing "part and" will break the logic + -- You can't do anything about it + if part and part ~= casts_RayInfo[id].CosmeticBulletObject then + if + canPierceCheckfn == nil + or canPierceCheckfn(ActivesRef[id], resultOfCast, segmentVelocity, casts_RayInfo[id].CosmeticBulletObject) == false + then + + casts_IsActivelyResimulating[id] = false + + if + casts_HighFidelityBehavior[id] == FastCastEnums.HighFidelityBehavior.Automatic + and casts_HighFidelitySegmentSize[id] > 0 + then + casts_CancelHighResCast[id] = false + + if casts_IsActivelyResimulating[id] then + QueueEvent(id, "CastTerminating", castTerminatingfn) + + warn( + "Cascading cast lag encountered! The caster attempted to perform a high fidelity cast before the previous one completed, resulting in exponential cast lag. Consider increasing HighFidelitySegmentSize." + ) + return + end + + casts_IsActivelyResimulating[id] = true + + local numSegmentsDecimal = rayDisplacement / casts_HighFidelitySegmentSize[id] + local numSegmentsReal = math.floor(numSegmentsDecimal) + --local realSegmentLength = rayDisplacement / numSegmentsReal + + if numSegmentsReal == 0 then + numSegmentsReal = 1 + end + + local timeIncrement = delta / numSegmentsReal + local subHitFound = false + + for segmentIndex = 1, numSegmentsReal do + if casts_CancelHighResCast[id] then + casts_CancelHighResCast[id] = false + break + end + + local subPosition = GetPositionAtTime( + totalDelta + (timeIncrement * segmentIndex), + origin, + initialVelocity, + acceleration + ) + local subVelocity = GetVelocityAtTime( + lastDelta + (timeIncrement * segmentIndex), + initialVelocity, + acceleration + ) + local subRayDir = subVelocity * delta + local subResult = castHandler(targetWorldRoot, subPosition, subRayDir, casts_RayInfo[id].Parameters) + + + --local subDisplacement = (subPosition - (subPosition + subVelocity)).Magnitude + + if subResult ~= nil then + subHitFound = true + + QueueVisualize(id, { + castStartCFrame = CFrame.new(subPosition, subPosition + subVelocity), + sub = true + } :: any) + + if + canPierceCheckfn == nil + or canPierceCheckfn(ActivesRef[id], subResult, subVelocity, casts_RayInfo[id].CosmeticBulletObject) == false + then + casts_IsActivelyResimulating[id] = false + QueueEvent(id, "Hit", subResult, subVelocity, casts_RayInfo[id].CosmeticBulletObject) + QueueEvent(id, "CastTerminating", castTerminatingfn) + QueueVisualize(id, { + atCF = CFrame.new(subResult.Position), + wasPierce = false, + sub = true + } :: any) + return + else + QueueEvent(id, "Pierced", subResult, subVelocity, casts_RayInfo[id].CosmeticBulletObject) + QueueVisualize(id, { + atCF = CFrame.new(subResult.Position), + wasPierce = true, + sub = true + } :: any) + end + else + QueueVisualize(id, { + castStartCFrame = CFrame.new(subPosition, subPosition+subVelocity), + sub = true + } :: any) + end + end + casts_IsActivelyResimulating[id] = false + if not subHitFound then + QueueEvent(id, "Hit", resultOfCast, segmentVelocity, casts_RayInfo[id].CosmeticBulletObject) + QueueVisualize(id, { + atCF = CFrame.new(point), + sub = false, + wasPierce = false + } :: any) + QueueEvent(id, "CastTerminating", castTerminatingfn) + return + end + else + QueueEvent(id, "Hit", resultOfCast, segmentVelocity, casts_RayInfo[id].CosmeticBulletObject) + QueueVisualize(id, { + atCF = CFrame.new(point), + sub = false, + wasPierce = false + } :: any) + QueueEvent(id, "CastTerminating", castTerminatingfn) + + return + end + else + QueueEvent(id, "Pierced", resultOfCast, segmentVelocity, casts_RayInfo[id].CosmeticBulletObject) + QueueVisualize(id, { + atCF = CFrame.new(point), + sub = false, + wasPierce = true + } :: any) + end + end + + if casts_DistanceCovered[id] >= casts_RayInfo[id].MaxDistance then + if resultOfCast then + QueueEvent(id, "Hit", resultOfCast, segmentVelocity, casts_RayInfo[id].CosmeticBulletObject) + QueueVisualize(id, { + atCF = CFrame.new(point), + sub = false, + wasPierce = false + }:: any) + end + QueueEvent(id, "CastTerminating", castTerminatingfn) + end +end + +local function UpdateCasts(delta: number) + for _, id in casts_ID do + if DebugLogging.Casting then + print("Casting for frame - UpdateCasts") + end + + + local Trajectory: TypeDef.CastTrajectory = casts_Trajectory[id] + + if casts_HighFidelitySegmentSize[id] <= 0 then + casts_HighFidelitySegmentSize[id] = 0.1 + end + + local FastCastEvents: TypeDef.FastCastEvents = casts_FastCastEvents[id] + + if casts_HighFidelityBehavior[id] == FastCastEnums.HighFidelityBehavior.Always then + local castTerminatingfn: TypeDef.OnCastTerminatingFunction? = nil + if FastCastEvents then + castTerminatingfn = casts_FastCastEventsModuleConfig[id].UseCastTerminating + and FastCastEvents.CastTerminating + or nil + end + + if casts_IsActivelyResimulating[id] then + QueueEvent(id, "CastTerminating", castTerminatingfn) + warn("Casading cast lag encountered! The caster attempted to perform a high fidelity cast before the previous one completed, resulting in exponential cast lag. Consider increasing HighFidelitySegmentSize.") + continue + end + casts_IsActivelyResimulating[id] = true + + local origin = Trajectory.Origin + local totalDelta = casts_TotalRunTime[id] - Trajectory.StartTime + local initialVelocity = Trajectory.InitialVelocity + local acceleration = Trajectory.Acceleration + + local lastPoint = GetPositionAtTime(totalDelta, origin, initialVelocity, acceleration) + + casts_TotalRunTime[id] += delta + + totalDelta = casts_TotalRunTime[id] - Trajectory.StartTime + + local currentPoint = GetPositionAtTime(totalDelta, origin, initialVelocity, acceleration) + local currentVelocity = GetVelocityAtTime(totalDelta, initialVelocity, acceleration) + local totalDisplacement = currentPoint - lastPoint + + local rayDir = totalDisplacement.Unit * currentVelocity.Magnitude * delta + + local RayInfo = casts_RayInfo[id] + local targetWorldRoot = RayInfo.WorldRoot + + local castHandler = castHandlers[casts_CastVariant[id].CastType] + local resultOfCast = castHandler(targetWorldRoot, lastPoint, rayDir, RayInfo.Parameters, casts_CastVariant[id]) + + local point = currentPoint + if resultOfCast ~= nil then + point = resultOfCast.Position + end + + local rayDisplacement = (point - lastPoint).Magnitude + casts_TotalRunTime[id] -= delta + + local numSegmentsDecimal = rayDisplacement / casts_HighFidelitySegmentSize[id] + local numSegmentsReal = math.floor(numSegmentsDecimal) + if numSegmentsReal == 0 then + numSegmentsReal = 1 + end + + local timeIncrement = delta / numSegmentsReal + + if DebugLogging.Calculation then + print("Performing subcast! Time increment: " .. timeIncrement .. ", num segments: " .. numSegmentsReal) + end + + local cast_nil = false + + for segmentIndex = 1, numSegmentsReal do + + -- In case when cast Destroyed or not exist + if ActivesRef[id] == nil then + cast_nil = true + end + + + if casts_CancelHighResCast[id] then + casts_CancelHighResCast[id] = false + break + end + + if DebugLogging.Segment then + print("[" .. segmentIndex .. "] Subcast of time increment " .. timeIncrement) + end + + SimulateCast(id, timeIncrement, FastCastEvents) + end + + if cast_nil then + casts_IsActivelyResimulating[id] = false + continue + end + + -- Double check again + if ActivesRef[id] == nil then + casts_IsActivelyResimulating[id] = false + continue + end + casts_IsActivelyResimulating[id] = false + else + SimulateCast(id, delta, FastCastEvents) + end + end + + task.synchronize() + + UpdateMotor6Ds() + BulkMoveTo() + + local eventsToProcess = queuedEvents + queuedEvents = {} + local visualizesToProcess = queuedVisualizes + queuedVisualizes = {} + + FireQueuedVisualizes(visualizesToProcess) + FireQueuedEvents(eventsToProcess) +end + +function ParallelSimulation.Start() + if ParallelSimulation.Connection then + warn("Already started") + return + end + + if RS:IsClient() then + ParallelSimulation.Connection = RS.PreSimulation:ConnectParallel(UpdateCasts) + else + ParallelSimulation.Connection = RS.Heartbeat:ConnectParallel(UpdateCasts) + end +end + +function ParallelSimulation.Stop() + if ParallelSimulation.Connection then + ParallelSimulation.Connection:Disconnect() + ParallelSimulation.Connection = nil + end +end + +return ParallelSimulation \ No newline at end of file diff --git a/src/SerialSimulation.luau b/src/SerialSimulation.luau new file mode 100644 index 00000000..8c7474c1 --- /dev/null +++ b/src/SerialSimulation.luau @@ -0,0 +1,894 @@ +--[[ + - Author: Mawin CK + - Date: 2026 +]] + +-- Services + +local RS = game:GetService("RunService") + +local TypeDef = require(script.Parent:WaitForChild("TypeDefinitions")) +local FastCastEnums = require(script.Parent:WaitForChild("FastCastEnums")) +local Config = require(script.Parent:WaitForChild("Config")) +local DebugLogging = Config.DebugLogging + +-- Constants +local EnumCastTypes = FastCastEnums.CastType +local DEFAULT_MAX_DISTANCE = 1000 +local FC_VIS_OBJ_NAME = "FastCastVisualizationObjects" + +-- Variables + +local casts_TotalRunTime = {} :: { [number]: number } +local casts_DistanceCovered = {} :: { [number]: number } +local casts_HighFidelitySegmentSize = {} :: { [number]: number } +local casts_HighFidelityBehavior = {} :: { [number]: number } +local casts_IsActivelySimulatingPierce = {} :: { [number]: boolean } +local casts_IsActivelyResimulating = {} :: { [number]: boolean } +local casts_CancelHighResCast = {} :: { [number]: boolean } +local casts_Trajectory = {} :: { [number]: TypeDef.CastTrajectory } +local casts_FastCastEventsConfig = {} :: { [number]: TypeDef.FastCastEventsConfig } +local casts_VisualizeCasts = {} :: { [number]: boolean } +local casts_VisualizeCastSettings = {} :: { [number]: TypeDef.VisualizeCastSettings } +local casts_RayInfo = {} :: { [number]: TypeDef.CastRayInfo } +local casts_UserData = {} :: { [number]: any } +local casts_CFrame = {} :: { [number]: CFrame } +local casts_CastType = {} :: { [number]: number } +local casts_CastVariant = {} :: { [number]: CastVariants } +local casts_Origin = {} :: { [number]: Vector3 } +local casts_Acceleration = {} :: { [number]: Vector3 } +local casts_MaxDistance = {} :: { [number]: number } +local casts_ActiveMotor6Ds = {} :: { [number]: Motor6D } +local casts_RayDisplacement = {} :: { [number]: number } + +-- Types + +type BlockcastVariant = { CastType: number, Size: Vector3 } +type SpherecastVariant = { CastType: number, Radius: number } +type CastVariants = BlockcastVariant | SpherecastVariant + + +type RayVisualizerVariant = { castLength: number} +type BlockVisualizerVariant = { size: Vector3 } +type SphereVisualizerVariant = { radius: number } +type CastVisualizerVariants = RayVisualizerVariant | BlockVisualizerVariant | SphereVisualizerVariant + +type QueuedEventData = { + eventType: string, + args: { any } +} + +type QueuedVisualizeCastData = { + castStartCFrame: CFrame, + sub: boolean +} + +type QueuedVisualizeHitData = { + atCF: CFrame, + wasPierce: boolean, + sub: boolean +} + +-- queued things + +local queuedEvents: { [number]: { QueuedEventData } } = {} +local queuedVisualizes: { [number]: { QueuedVisualizeCastData | QueuedVisualizeHitData }} = {} + +local castHandlers = { + [EnumCastTypes.Raycast] = function( + targetWorldRoot: WorldRoot, + origin: Vector3, + direction: Vector3, + params: RaycastParams + ) + return targetWorldRoot:Raycast(origin, direction, params) + end, + [EnumCastTypes.Blockcast] = function( + targetWorldRoot: WorldRoot, + origin: Vector3, + direction: Vector3, + params: RaycastParams, + variant: BlockcastVariant + ) + return targetWorldRoot:Blockcast(CFrame.new(origin), variant.Size, direction, params) + end, + [EnumCastTypes.Spherecast] = function( + targetWorldRoot: WorldRoot, + origin: Vector3, + direction: Vector3, + params: RaycastParams, + variant: SpherecastVariant + ) + return targetWorldRoot:Spherecast(origin, variant.Radius, direction, params) + end +} + +-- Utils + +local function GetPositionAtTime( + t: number, + origin: Vector3, + initialVelocity: Vector3, + acceleration: Vector3 +): Vector3 + local force = Vector3.new( + (acceleration.X * t ^ 2) / 2, + (acceleration.Y * t ^ 2) / 2, + (acceleration.Z * t ^ 2) / 2 + ) + return origin + (initialVelocity * t) + force +end + +local function GetVelocityAtTime(time: number, initialVelocity: Vector3, acceleration: Vector3): Vector3 + return initialVelocity + acceleration * time +end + +local function DebrisAdd(obj: Instance, Lifetime: number) + if not obj then + return + end + if Lifetime <= 0 then + obj:Destroy() + return + end + + task.delay(Lifetime, function() + obj:Destroy() + end) +end + +local function GetFastCastVisualizationContainer(): Instance + local fcVisualizationObjects = workspace.Terrain:FindFirstChild(FC_VIS_OBJ_NAME) + if fcVisualizationObjects then + return fcVisualizationObjects + end + + fcVisualizationObjects = Instance.new("Folder") + fcVisualizationObjects.Name = FC_VIS_OBJ_NAME + fcVisualizationObjects.Archivable = false + fcVisualizationObjects.Parent = workspace.Terrain + return fcVisualizationObjects +end + +local function DbgVisualizeRaySegment( + castStartCFrame: CFrame, + VisualizeCastSettings: TypeDef.VisualizeCastSettings, + variant: RayVisualizerVariant +): ConeHandleAdornment? + local adornment = Instance.new("ConeHandleAdornment") + adornment.Adornee = workspace.Terrain + adornment.CFrame = castStartCFrame + adornment.Height = variant.castLength + adornment.Color3 = VisualizeCastSettings.Debug_SegmentColor + adornment.Radius = VisualizeCastSettings.Debug_SegmentSize + adornment.Transparency = VisualizeCastSettings.Debug_SegmentTransparency + adornment.Parent = GetFastCastVisualizationContainer() + + DebrisAdd(adornment, VisualizeCastSettings.Debug_RayLifetime) + return adornment +end + +local function DbgVisualizeBlockSegment( + castStartCFrame: CFrame, + VisualizeCastSetting: TypeDef.VisualizeCastSettings, + variant: BlockVisualizerVariant +): BoxHandleAdornment? + local adornment = Instance.new("BoxHandleAdornment") + adornment.Adornee = workspace.Terrain + adornment.CFrame = castStartCFrame + --adornment.Height = castLength + + adornment.Size = variant.size + adornment.Color3 = VisualizeCastSetting.Debug_SegmentColor + adornment.Transparency = VisualizeCastSetting.Debug_SegmentTransparency + + adornment.Parent = GetFastCastVisualizationContainer() + + DebrisAdd(adornment, VisualizeCastSetting.Debug_RayLifetime) + return adornment +end + +local function DbgVisualizeSphereSegment( + castStartCFrame: CFrame, + VisualizeCastSetting: TypeDef.VisualizeCastSettings, + variant: SphereVisualizerVariant +): SphereHandleAdornment? + local adornment = Instance.new("SphereHandleAdornment") + adornment.Adornee = workspace.Terrain + adornment.CFrame = castStartCFrame + --adornment.Height = castLength + adornment.Radius = variant.radius + --adornment.Size = Vector3.new(size.X, size.Y, size.Z + castLength) + adornment.Color3 = VisualizeCastSetting.Debug_SegmentColor + adornment.Transparency = VisualizeCastSetting.Debug_SegmentTransparency + + adornment.Parent = GetFastCastVisualizationContainer() + + DebrisAdd(adornment, VisualizeCastSetting.Debug_RayLifetime) + return adornment +end + +local function DbgVisualizeHit( + atCF: CFrame, + wasPierce: boolean, + VisualizeCastSettings: TypeDef.VisualizeCastSettings +): SphereHandleAdornment? + local adornment = Instance.new("SphereHandleAdornment") + adornment.Adornee = workspace.Terrain + adornment.CFrame = atCF + -- Alert! someone is Mawining it!!!!! + adornment.Radius = (wasPierce == false) and VisualizeCastSettings.Debug_HitSize + or VisualizeCastSettings.Debug_RayPierceSize + adornment.Transparency = (wasPierce == false) and VisualizeCastSettings.Debug_HitTransparency + or VisualizeCastSettings.Debug_RayPierceTransparency + adornment.Color3 = (wasPierce == false) and VisualizeCastSettings.Debug_HitColor + or VisualizeCastSettings.Debug_RayPierceColor + adornment.Parent = GetFastCastVisualizationContainer() + + DebrisAdd(adornment, VisualizeCastSettings.Debug_HitLifetime) + return adornment +end + +local Visualizers = { + [EnumCastTypes.Raycast] = DbgVisualizeRaySegment, + [EnumCastTypes.Blockcast] = DbgVisualizeBlockSegment, + [EnumCastTypes.Spherecast] = DbgVisualizeSphereSegment +} + +-- SerialSimulation + +local SerialSimulation = {} +SerialSimulation.__index = SerialSimulation +SerialSimulation.__type = "SerialSimulation" + +function SerialSimulation:Register(cast: any) + local id = cast.ID + + casts_TotalRunTime[id] = cast.StateInfo.TotalRuntime or 0 + casts_DistanceCovered[id] = 0 + casts_HighFidelitySegmentSize[id] = cast.StateInfo.HighFidelitySegmentSize or 0.1 + casts_HighFidelityBehavior[id] = cast.StateInfo.HighFidelityBehavior or 0 + casts_IsActivelySimulatingPierce[id] = false + casts_IsActivelyResimulating[id] = false + casts_CancelHighResCast[id] = false + casts_Trajectory[id] = cast.StateInfo.Trajectory + casts_FastCastEventsConfig[id] = cast.StateInfo.FastCastEventsConfig + casts_VisualizeCasts[id] = cast.StateInfo.VisualizeCasts + casts_VisualizeCastSettings[id] = cast.StateInfo.VisualizeCastSettings + casts_RayInfo[id] = cast.RayInfo + casts_UserData[id] = cast.UserData + casts_CastType[id] = cast.CastVariant.CastType + casts_CastVariant[id] = cast.CastVariant + casts_Origin[id] = cast.StateInfo.Trajectory.Origin + casts_Acceleration[id] = cast.StateInfo.Trajectory.Acceleration + casts_MaxDistance[id] = cast.RayInfo.MaxDistance or DEFAULT_MAX_DISTANCE + table.insert(self.casts_ID, id) + self.casts_ID_Index[id] = #self.casts_ID + + local position = GetPositionAtTime( + casts_TotalRunTime[id], + casts_Trajectory[id].Origin, + casts_Trajectory[id].InitialVelocity, + casts_Trajectory[id].Acceleration + ) + casts_CFrame[id] = CFrame.new(position) + + cast.CFrame = casts_CFrame[id] + + if self.CurrentMovementMode == "Motor6D" and self.MovementEnabled then + local cosmeticPart = casts_RayInfo[id] and casts_RayInfo[id].CosmeticBulletObject + if cosmeticPart and self.BaseCastRef and self.BaseCastRef._GetMotor6D then + local motor6d = self.BaseCastRef:_GetMotor6D(cosmeticPart) + casts_ActiveMotor6Ds[id] = motor6d + end + end + + queuedEvents[id] = {} +end + +function SerialSimulation:Unregister(castID: number) + casts_TotalRunTime[castID] = nil + casts_DistanceCovered[castID] = nil + casts_HighFidelitySegmentSize[castID] = nil + casts_HighFidelityBehavior[castID] = nil + casts_IsActivelySimulatingPierce[castID] = nil + casts_IsActivelyResimulating[castID] = nil + casts_CancelHighResCast[castID] = nil + casts_Trajectory[castID] = nil + casts_FastCastEventsConfig[castID] = nil + casts_VisualizeCasts[castID] = nil + casts_VisualizeCastSettings[castID] = nil + casts_RayInfo[castID] = nil + casts_UserData[castID] = nil + casts_CFrame[castID] = nil + casts_CastType[castID] = nil + casts_CastVariant[castID] = nil + casts_Origin[castID] = nil + casts_Acceleration[castID] = nil + casts_MaxDistance[castID] = nil + casts_RayDisplacement[castID] = nil + + if casts_ActiveMotor6Ds[castID] then + if self.BaseCastRef and self.BaseCastRef._ReturnMotor6D then + self.BaseCastRef:_ReturnMotor6D(casts_ActiveMotor6Ds[castID]) + end + casts_ActiveMotor6Ds[castID] = nil + end + + local idx = self.casts_ID_Index[castID] + if idx then + local lastID = self.casts_ID[#self.casts_ID] + self.casts_ID[idx] = lastID + self.casts_ID_Index[lastID] = idx + self.casts_ID[#self.casts_ID] = nil + self.casts_ID_Index[castID] = nil + end + + queuedEvents[castID] = nil + queuedVisualizes[castID] = nil +end + +function SerialSimulation:SetMovementModeEnabled(enabled: boolean, mode: "BulkMoveTo" | "Motor6D") + local oldMode = self.CurrentMovementMode + self.CurrentMovementMode = mode + self.MovementEnabled = enabled + + if oldMode == "Motor6D" and mode ~= "Motor6D" then + for id, motor6d in casts_ActiveMotor6Ds do + if self.BaseCastRef and self.BaseCastRef._ReturnMotor6D then + self.BaseCastRef:_ReturnMotor6D(motor6d) + end + casts_ActiveMotor6Ds[id] = nil + end + end + + if mode == "Motor6D" and enabled and oldMode ~= "Motor6D" then + for _, id in self.casts_ID do + if not casts_ActiveMotor6Ds[id] then + local cosmeticPart = casts_RayInfo[id] and casts_RayInfo[id].CosmeticBulletObject + if cosmeticPart and self.BaseCastRef and self.BaseCastRef._GetMotor6D then + local motor6d = self.BaseCastRef:_GetMotor6D(cosmeticPart) + casts_ActiveMotor6Ds[id] = motor6d + end + end + end + end +end + +function SerialSimulation:QueueVisualize(castID: number, visualizeData: QueuedVisualizeCastData | QueuedVisualizeHitData) + if not casts_VisualizeCasts[castID] then + return + end + + if not queuedVisualizes[castID] then + queuedVisualizes[castID] = {} + end + + table.insert(queuedVisualizes[castID], visualizeData) +end + +function SerialSimulation:FireQueuedVisualizes(unFiredVisualizes: { [number]: { QueuedVisualizeCastData | QueuedVisualizeHitData }}) + local sortedIDs = {} + for id in unFiredVisualizes do + table.insert(sortedIDs, id) + end + table.sort(sortedIDs) + + for _, castID in sortedIDs do + local visualizesList = unFiredVisualizes[castID] + if not visualizesList or not next(visualizesList) then + continue + end + + for _, visualizeData in visualizesList do + -- VisualizeHit + if visualizeData.atCF then + if DebugLogging.Hit and not visualizeData.wasPierce and not visualizeData.sub then + print("Hit was successful. Terminating.") + end + if DebugLogging.Pierced and visualizeData.wasPierce and not visualizeData.sub then + print("Piercing function returned TRUE to pierce this part.") + end + DbgVisualizeHit(visualizeData.atCF, visualizeData.wasPierce, casts_VisualizeCastSettings[castID]) + continue + end + -- VisualizeCasts + if DebugLogging.Segment and visualizeData.sub then + print("Subcast visualization") + end + local castType = casts_CastType[castID] + local castVisualizer = Visualizers[castType] + if not castVisualizer then + continue + end + local visualizerVariant: CastVisualizerVariants + if castType == EnumCastTypes.Raycast then + visualizerVariant = { castLength = casts_RayDisplacement[castID] or 1 } + elseif castType == EnumCastTypes.Blockcast then + visualizerVariant = { size = (casts_CastVariant[castID] :: BlockcastVariant).Size } + else + visualizerVariant = { radius = (casts_CastVariant[castID] :: SpherecastVariant).Radius } + end + castVisualizer(visualizeData.castStartCFrame, casts_VisualizeCastSettings[castID], visualizerVariant) + end + end +end + +function SerialSimulation:QueueEvent(castID: number, eventType: string, ...: any) + local args = { ... } + if not queuedEvents[castID] then + queuedEvents[castID] = {} + end + table.insert(queuedEvents[castID], { + eventType = eventType, + args = args + }) +end + +function SerialSimulation:FireQueuedEvents(unFiredEvents: { [number]: { QueuedEventData } }) + local sortedIDs = {} + for id in unFiredEvents do + table.insert(sortedIDs, id) + end + table.sort(sortedIDs) + + local eventsFunction = self.Events + + for _, castID in sortedIDs do + local eventList = unFiredEvents[castID] + if not eventList or not next(eventList) then + continue + end + + for _, event in eventList do + local cast = self.ActivesRef[castID] + if not cast then + continue + end + + local eventType: string = event.eventType + local args: { any } = event.args + + local eventConfig = casts_FastCastEventsConfig[castID] + + if eventType == "LengthChanged" then + local lastPoint = args[1] + local rayDir = args[2] + local rayDisplacement = args[3] + + if eventConfig and eventConfig.UseLengthChanged and eventsFunction.LengthChanged then + eventsFunction.LengthChanged(cast, lastPoint, rayDir, rayDisplacement) + end + + elseif eventType == "Hit" then + local result = args[1] + local velocity = args[2] + local cosmeticBulletObject = args[3] + + if eventConfig and eventConfig.UseHit and eventsFunction.Hit then + eventsFunction.Hit(cast, result, velocity, cosmeticBulletObject) + end + + elseif eventType == "Pierced" then + local result = args[1] + local velocity = args[2] + local cosmeticBulletObject = args[3] + + if eventConfig and eventConfig.UsePierced and eventsFunction.Pierced then + eventsFunction.Pierced(cast, result, velocity, cosmeticBulletObject) + end + + elseif eventType == "CastTerminating" then + self.BaseCastRef:_TerminateCast(castID, eventsFunction.CastTerminating) + self:Unregister(castID) + end + end + end +end + +function SerialSimulation:BulkMoveTo() + if self.CurrentMovementMode ~= "BulkMoveTo" or not self.MovementEnabled then + return + end + + local parts = table.create(#self.casts_ID) + local cframes = table.create(#self.casts_ID) + + for _, id in self.casts_ID do + local cosmeticPart = casts_RayInfo[id] and casts_RayInfo[id].CosmeticBulletObject + if cosmeticPart and cosmeticPart.Parent then + table.insert(parts, cosmeticPart) + table.insert(cframes, casts_CFrame[id]) + end + end + + if #parts > 0 then + workspace:BulkMoveTo(parts, cframes, Enum.BulkMoveMode.FireCFrameChanged) + end +end + +function SerialSimulation:UpdateMotor6Ds() + if self.CurrentMovementMode ~= "Motor6D" or not self.MovementEnabled then + return + end + + for id, motor6d in casts_ActiveMotor6Ds do + if motor6d and motor6d.Parent then + motor6d.Transform = casts_CFrame[id] + end + end +end + +-- RS +function SerialSimulation:SimulateCast( + id: number, + delta: number, + CanPiercefn: TypeDef.CanPierceFunction? +) + if DebugLogging.Casting then + print("Casting for frame - SimulateCast") + end + local trajectory = casts_Trajectory[id] + + local origin = trajectory.Origin + local totalDelta = casts_TotalRunTime[id] - trajectory.StartTime + local initialVelocity = trajectory.InitialVelocity + local acceleration = trajectory.Acceleration + + local lastPoint = GetPositionAtTime(totalDelta, origin, initialVelocity, acceleration) + + local lastDelta = casts_TotalRunTime[id] - trajectory.StartTime + + casts_TotalRunTime[id] += delta + + totalDelta = casts_TotalRunTime[id] - trajectory.StartTime + + local currentTarget = GetPositionAtTime(totalDelta, origin, initialVelocity, acceleration) + local segmentVelocity = GetVelocityAtTime(totalDelta, initialVelocity, acceleration) + local totalDisplacement = currentTarget - lastPoint + + local rayDir = totalDisplacement + + local targetWorldRoot = casts_RayInfo[id].WorldRoot + + local castHandler = castHandlers[casts_CastVariant[id].CastType] + local resultOfCast = castHandler(targetWorldRoot, lastPoint, rayDir, casts_RayInfo[id].Parameters, casts_CastVariant[id]) + + local point = currentTarget + local part: Instance? = nil + + if resultOfCast ~= nil then + point = resultOfCast.Position + part = resultOfCast.Instance + end + + local rayDisplacement = (point - lastPoint).Magnitude + casts_RayDisplacement[id] = rayDisplacement + + casts_CFrame[id] = (if rayDir ~= Vector3.new() then CFrame.new(lastPoint, lastPoint + rayDir) else CFrame.new(lastPoint)) * CFrame.new(0, 0, -rayDisplacement / 2) + + self:QueueEvent(id, "LengthChanged", lastPoint, rayDir.Unit, rayDisplacement) + + casts_DistanceCovered[id] += rayDisplacement + + if delta > 0 then + self:QueueVisualize( + id, + { + castStartCFrame = CFrame.new(lastPoint, lastPoint+rayDir), + sub = false + } :: any + ) + end + + -- NOTE: Please dont remove "part and" + -- Why? basically when part doesn't exist it will do nothing, but removing "part and" will break the logic + -- You can't do anything about it + if part and part ~= casts_RayInfo[id].CosmeticBulletObject then + if + CanPiercefn == nil + or CanPiercefn(self.ActivesRef[id], resultOfCast, segmentVelocity, casts_RayInfo[id].CosmeticBulletObject) == false + then + + casts_IsActivelyResimulating[id] = false + + if + casts_HighFidelityBehavior[id] == FastCastEnums.HighFidelityBehavior.Automatic + and casts_HighFidelitySegmentSize[id] > 0 + then + casts_CancelHighResCast[id] = false + + if casts_IsActivelyResimulating[id] then + self:QueueEvent(id, "CastTerminating") + + warn( + "Cascading cast lag encountered! The caster attempted to perform a high fidelity cast before the previous one completed, resulting in exponential cast lag. Consider increasing HighFidelitySegmentSize." + ) + return + end + + casts_IsActivelyResimulating[id] = true + + local numSegmentsDecimal = rayDisplacement / casts_HighFidelitySegmentSize[id] + local numSegmentsReal = math.floor(numSegmentsDecimal) + --local realSegmentLength = rayDisplacement / numSegmentsReal + + if numSegmentsReal == 0 then + numSegmentsReal = 1 + end + + local timeIncrement = delta / numSegmentsReal + local subHitFound = false + + for segmentIndex = 1, numSegmentsReal do + if casts_CancelHighResCast[id] then + casts_CancelHighResCast[id] = false + break + end + + local subPosition = GetPositionAtTime( + totalDelta + (timeIncrement * segmentIndex), + origin, + initialVelocity, + acceleration + ) + local subVelocity = GetVelocityAtTime( + lastDelta + (timeIncrement * segmentIndex), + initialVelocity, + acceleration + ) + local subRayDir = subVelocity * delta + local subResult = castHandler(targetWorldRoot, subPosition, subRayDir, casts_RayInfo[id].Parameters) + + + --local subDisplacement = (subPosition - (subPosition + subVelocity)).Magnitude + + if subResult ~= nil then + subHitFound = true + + self:QueueVisualize(id, { + castStartCFrame = CFrame.new(subPosition, subPosition + subVelocity), + sub = true + } :: any) + + --subDispalcement = (subPosition - subResult.Position).Magnitude + + if + CanPiercefn == nil + or CanPiercefn(self.ActivesRef[id], subResult, subVelocity, casts_RayInfo[id].CosmeticBulletObject) == false + then + casts_IsActivelyResimulating[id] = false + self:QueueEvent(id, "Hit", subResult, subVelocity, casts_RayInfo[id].CosmeticBulletObject) + self:QueueEvent(id, "CastTerminating") + self:QueueVisualize(id, { + atCF = CFrame.new(subResult.Position), + wasPierce = false, + sub = true + }:: any) + + return + else + self:QueueEvent(id, "Pierced", subResult, subVelocity, casts_RayInfo[id].CosmeticBulletObject) + self:QueueVisualize(id, { + atCF = CFrame.new(subResult.Position), + wasPierce = true, + sub = true + }:: any) + end + else + self:QueueVisualize(id, { + castStartCFrame = CFrame.new(subPosition, subPosition+subVelocity), + sub = true + }:: any) + end + end + casts_IsActivelyResimulating[id] = false + if not subHitFound then + self:QueueEvent(id, "Hit", resultOfCast, segmentVelocity, casts_RayInfo[id].CosmeticBulletObject) + self:QueueVisualize(id, { + atCF = CFrame.new(point), + sub = false, + wasPierce = false + }:: any) + self:QueueEvent(id, "CastTerminating") + return + end + else + self:QueueEvent(id, "Hit", resultOfCast, segmentVelocity, casts_RayInfo[id].CosmeticBulletObject) + self:QueueVisualize(id, { + atCF = CFrame.new(point), + sub = false, + wasPierce = false + }:: any) + self:QueueEvent(id, "CastTerminating") + + return + end + else + self:QueueEvent(id, "Pierced", resultOfCast, segmentVelocity, casts_RayInfo[id].CosmeticBulletObject) + self:QueueVisualize(id, { + atCF = CFrame.new(point), + sub = false, + wasPierce = true + }:: any) + end + end + + if casts_DistanceCovered[id] >= casts_RayInfo[id].MaxDistance then + if resultOfCast then + self:QueueEvent(id, "Hit", resultOfCast, segmentVelocity, casts_RayInfo[id].CosmeticBulletObject) + self:QueueVisualize(id, { + atCF = CFrame.new(point), + sub = false, + wasPierce = false + }:: any) + end + self:QueueEvent(id, "CastTerminating") + end +end + +function SerialSimulation:UpdateCasts(delta: number) + for _, id in self.casts_ID do + if DebugLogging.Casting then + print("Casting for frame - UpdateCasts") + end + + + local Trajectory: TypeDef.CastTrajectory = casts_Trajectory[id] + + if casts_HighFidelitySegmentSize[id] <= 0 then + casts_HighFidelitySegmentSize[id] = 0.1 + end + + if casts_HighFidelityBehavior[id] == FastCastEnums.HighFidelityBehavior.Always then + + if casts_IsActivelyResimulating[id] then + self:QueueEvent(id, "CastTerminating") + warn("Casading cast lag encountered! The caster attempted to perform a high fidelity cast before the previous one completed, resulting in exponential cast lag. Consider increasing HighFidelitySegmentSize.") + continue + end + casts_IsActivelyResimulating[id] = true + + local origin = Trajectory.Origin + local totalDelta = casts_TotalRunTime[id] - Trajectory.StartTime + local initialVelocity = Trajectory.InitialVelocity + local acceleration = Trajectory.Acceleration + + local lastPoint = GetPositionAtTime(totalDelta, origin, initialVelocity, acceleration) + + casts_TotalRunTime[id] += delta + + totalDelta = casts_TotalRunTime[id] - Trajectory.StartTime + + local currentPoint = GetPositionAtTime(totalDelta, origin, initialVelocity, acceleration) + local currentVelocity = GetVelocityAtTime(totalDelta, initialVelocity, acceleration) + local totalDisplacement = currentPoint - lastPoint + + local rayDir = totalDisplacement.Unit * currentVelocity.Magnitude * delta + + local RayInfo = casts_RayInfo[id] + local targetWorldRoot = RayInfo.WorldRoot + + local castHandler = castHandlers[casts_CastVariant[id].CastType] + local resultOfCast = castHandler(targetWorldRoot, lastPoint, rayDir, RayInfo.Parameters, casts_CastVariant[id]) + + local point = currentPoint + if resultOfCast ~= nil then + point = resultOfCast.Position + end + + local rayDisplacement = (point - lastPoint).Magnitude + casts_TotalRunTime[id] -= delta + + local numSegmentsDecimal = rayDisplacement / casts_HighFidelitySegmentSize[id] + local numSegmentsReal = math.floor(numSegmentsDecimal) + if numSegmentsReal == 0 then + numSegmentsReal = 1 + end + + local timeIncrement = delta / numSegmentsReal + + if DebugLogging.Calculation then + print("Performing subcast! Time increment: " .. timeIncrement .. ", num segments: " .. numSegmentsReal) + end + + local cast_nil = false + for segmentIndex = 1, numSegmentsReal do + + -- In case when cast Destroyed or not exist + if self.ActivesRef[id] == nil then + cast_nil = true + end + + + if casts_CancelHighResCast[id] then + casts_CancelHighResCast[id] = false + break + end + + if DebugLogging.Segment then + print("[" .. segmentIndex .. "] Subcast of time increment " .. timeIncrement) + end + + self:SimulateCast(id, timeIncrement, self.Events.CanPierce) + end + + if cast_nil then + casts_IsActivelyResimulating[id] = false + continue + end + + -- Double check again + if self.ActivesRef[id] == nil then + casts_IsActivelyResimulating[id] = false + continue + end + casts_IsActivelyResimulating[id] = false + else + self:SimulateCast(id, delta, self.Events.CanPierce) + end + end + + -- BulkMoveTo, UpdateMotor6Ds, FireQueuedEvents + + self:UpdateMotor6Ds() + self:BulkMoveTo() + + local eventsToProcess = queuedEvents + queuedEvents = {} + local visualizesToProcess = queuedVisualizes + queuedVisualizes = {} + + self:FireQueuedVisualizes(visualizesToProcess) + self:FireQueuedEvents(eventsToProcess) +end + +function SerialSimulation.new() + local self = setmetatable({}, SerialSimulation) + self.Connection = nil + self.CurrentMovementMode = "BulkMoveTo" + self.MovementEnabled = true + self.Events = {} + self.BaseCastRef = nil + self.ActivesRef = nil + self.casts_ID = {} + self.casts_ID_Index = {} + return self +end + +function SerialSimulation:Init(baseCastRef: any, events: TypeDef.FastCastEvents) + self.BaseCastRef = baseCastRef + self.ActivesRef = baseCastRef.Actives + self.Events = events +end + +function SerialSimulation:Start() + if self.Connection then + warn("Already started") + return + end + + if RS:IsClient() then + self.Connection = RS.PreSimulation:Connect(function(delta: number) + self:UpdateCasts(delta) + end) + else + self.Connection = RS.Heartbeat:Connect(function(delta: number) + self:UpdateCasts(delta) + end) + end +end + +function SerialSimulation:Stop() + if self.Connection then + self.Connection:Disconnect() + self.Connection = nil + end +end + +-- Utils +function SerialSimulation:_UpdateEvents(eventName: string, newEventfn: (...any) -> ()) + self.Events[eventName] = newEventfn +end + +return SerialSimulation \ No newline at end of file diff --git a/src/FastCast2/TypeDefinitions.luau b/src/TypeDefinitions.luau similarity index 50% rename from src/FastCast2/TypeDefinitions.luau rename to src/TypeDefinitions.luau index 886fde70..0855244f 100644 --- a/src/FastCast2/TypeDefinitions.luau +++ b/src/TypeDefinitions.luau @@ -3,7 +3,7 @@ --[[ - Author : Mawin CK - Date : 2025 - -- Verison : 0.0.9 + ]] --[=[ @@ -13,11 +13,8 @@ Type definitions for strict-typing. ]=] -local SignalModule = require(script.Parent:WaitForChild("Signal")) local Dispatcher = require(script.Parent:WaitForChild("FastCastVMs")) -type Signal = SignalModule.Signal - --[=[ @type vaildcast ActiveCastData | ActiveBlockcastData | ActiveSpherecastData @within TypeDefinitions @@ -126,53 +123,61 @@ export type OnCastFireFunction = ( behavior: FastCastBehavior ) -> () +-- Debug + --[=[ - @type ObjectCache { GetPart: (ObjectCache, PartCFrame: CFrame) -> BasePart, ReturnPart: (ObjectCache, Part: BasePart) -> (), Update: (ObjectCache) -> (), ExpandCache: (ObjectCache, Amount: number) -> (), SetExpandAmount: (ObjectCache, Amount: number) -> (), IsInUse: (ObjectCache, Object: BasePart) -> boolean, Destroy: (ObjectCache) -> () } + @type VisualizeCastSettings { Debug_SegmentColor: Color3, Debug_SegmentTransparency: number, Debug_SegmentSize: number, Debug_HitColor: Color3, Debug_HitTransparency: number, Debug_HitSize: number, Debug_RayPierceColor: Color3, Debug_RayPierceTransparency: number, Debug_RayPierceSize: number, Debug_RayLifetime: number, Debug_HitLifetime: number } @within TypeDefinitions - Represents a ObjectCache object. + Debug visualization settings for casts. ]=] -export type ObjectCache = { - GetPart: (ObjectCache, PartCFrame: CFrame) -> BasePart, - ReturnPart: (ObjectCache, Part: BasePart) -> (), - Update: (ObjectCache) -> (), - ExpandCache: (ObjectCache, Amount: number) -> (), - SetExpandAmount: (ObjectCache, Amount: number) -> (), - IsInUse: (ObjectCache, Object: BasePart) -> boolean, - Destroy: (ObjectCache) -> () +export type VisualizeCastSettings = { + Debug_SegmentColor: Color3, + Debug_SegmentTransparency: number, + Debug_SegmentSize: number, + + Debug_HitColor: Color3, + Debug_HitTransparency: number, + Debug_HitSize: number, + + Debug_RayPierceColor: Color3, + Debug_RayPierceTransparency: number, + Debug_RayPierceSize: number, + + Debug_RayLifetime: number, + Debug_HitLifetime: number, } --[=[ - @type Caster { WorldRoot: WorldRoot, LengthChanged: Signal | OnLengthChangedFunction, Hit: Signal | OnHitFunction, Pierced: Signal | OnPiercedFunction, CastTerminating: Signal | OnCastTerminatingFunction, CastFire: Signal | OnCastFireFunction, Dispatcher: Dispatcher.Dispatcher, ObjectCache: ObjectCache, AlreadyInit: boolean, ObjectCacheEnabled: boolean, BulkMoveEnabled: boolean, FastCastEventsModule: FastCastEventsModule, Init: ( self: Caster, numWorkers: number, newParent: Folder, newName: string, ContainerParent: Folder, VMContainerName: string, VMname: string, useBulkMoveTo: boolean, FastCastEventsModule: ModuleScript, useObjectCache: boolean, Template: BasePart | Model, CacheSize: number, CacheHolder: Instance ) -> (), RaycastFire: ( Caster, Origin: Vector3, Direction: Vector3, Velocity: Vector3 | number, Behavior: FastCastBehavior ) -> (), BlockcastFire: ( self: Caster, Origin: Vector3, Size: Vector3, Direction: Vector3, Velocity: Vector3 | number, Behavior: FastCastBehavio ) -> (), SetBulkMoveEnabled: (self: Caster, enabled: boolean) -> (), SetObjectCacheEnabled: ( self: Caster, enabled: boolean, Template: BasePart | Model, CacheSize: number, CacheHolder: Instance ) -> (), SetFastCastEventsModule: (self: Caster, moduleScript: ModuleScript) -> (), AddVelocityCast: (Caster, cast: vaildcast, velocity: Vector3) -> (), SetVelocityCast: (Caster, cast: vaildcast, velocity: Vector3) -> (), GetVelocityCast: (Caster, cast: vaildcast, velocity: Vector3) -> Vector3, AddAccelerationCast: (Caster, cast: vaildcast) -> Vector3, GetAccelerationCast: (Caster, cast: vaildcast) -> Vector3, SetAccelerationCast: (Caster, cast: vaildcast, acceleration: Vector3) -> (), GetPositionCast: (Caster, cast: vaildcast, Position: Vector3) -> Vector3, AddPositionCast: (Caster, cast: vaildcast, Position: Vector3) -> (), ResumeCast: (Caster, cast: vaildcast) -> (), PauseCast: (Caster, cast: vaildcast) -> (), SyncChangesToCast: (Caster, cast: vaildcast) -> (), TerminateCast: (Caster, cast: vaildcast) -> (), Destroy: (Caster) -> () } + @type CasterParallel { WorldRoot: WorldRoot, LengthChanged: OnLengthChangedFunction, Hit: OnHitFunction, Pierced: OnPiercedFunction, CastTerminating: OnCastTerminatingFunction, CastFire: OnCastFireFunction, Dispatcher: Dispatcher.Dispatcher, AlreadyInit: boolean, ObjectCacheEnabled: boolean, MovementMode: "BulkMoveTo" | "Motor6D", FastCastEventsModule: FastCastEventsModule, Init: ( self: CasterParallel, numWorkers: number, newParent: Folder, newName: string, ContainerParent: Folder, VMContainerName: string, VMname: string, MovementMode: "BulkMoveTo" | "Motor6D", FastCastEventsModule: ModuleScript, useObjectCache: boolean, Template: BasePart | Model, CacheSize: number, CacheHolder: Instance ) -> (), RaycastFire: ( CasterParallel, Origin: Vector3, Direction: Vector3, Velocity: Vector3 | number, Behavior: FastCastBehavior? ) -> (), BlockcastFire: ( self: CasterParallel, Origin: Vector3, Size: Vector3, Direction: Vector3, Velocity: Vector3 | number, Behavior: FastCastBehavior? ) -> (), SpherecastFire: ( self: CasterParallel, Origin: Vector3, Radius: number, Direction: Vector3, Velocity: Vector3 | number, Behavior: FastCastBehavior? ) -> (), SetMovementModeEnabled: ( mode: "BulkMoveTo" | "Motor6D", enabled: boolean ) -> (), SetObjectCacheEnabled: ( self: CasterParallel, enabled: boolean, Template: BasePart | Model, CacheSize: number, CacheHolder: Instance ) -> (), SetFastCastEventsModule: (self: CasterParallel, moduleScript: ModuleScript) -> (), AddVelocityCast: (CasterParallel, cast: vaildcast, velocity: Vector3) -> (), SetVelocityCast: (CasterParallel, cast: vaildcast, velocity: Vector3) -> (), GetVelocityCast: (CasterParallel, cast: vaildcast) -> Vector3, AddAccelerationCast: (CasterParallel, cast: vaildcast, acceleration: Vector3) -> Vector3, SetAccelerationCast: (CasterParallel, cast: vaildcast, acceleration: Vector3) -> (), GetAccelerationCast: (CasterParallel, cast: vaildcast) -> Vector3, AddPositionCast: (CasterParallel, cast: vaildcast, Position: Vector3) -> (), GetPositionCast: (CasterParallel, cast: vaildcast) -> Vector3, SyncChangesToCast: (CasterParallel, cast: vaildcast) -> (), TerminateCast: (CasterParallel, cast: vaildcast) -> (), Destroy: (CasterParallel) -> () } @within TypeDefinitions - Represents a Caster. + Represents a Caster Parallel. ]=] -export type Caster = { +export type CasterParallel = { WorldRoot: WorldRoot, - LengthChanged: Signal | OnLengthChangedFunction, - Hit: Signal | OnHitFunction, - Pierced: Signal | OnPiercedFunction, - CastTerminating: Signal | OnCastTerminatingFunction, - CastFire: Signal | OnCastFireFunction, + LengthChanged: OnLengthChangedFunction, + Hit: OnHitFunction, + Pierced: OnPiercedFunction, + CastTerminating: OnCastTerminatingFunction, + CastFire: OnCastFireFunction, Dispatcher: Dispatcher.Dispatcher, - ObjectCache: ObjectCache, AlreadyInit: boolean, ObjectCacheEnabled: boolean, - BulkMoveEnabled: boolean, + MovementMode: "BulkMoveTo" | "Motor6D", FastCastEventsModule: FastCastEventsModule, Init: ( - self: Caster, + self: CasterParallel, numWorkers: number, newParent: Folder, newName: string, ContainerParent: Folder, VMContainerName: string, VMname: string, - useBulkMoveTo: boolean, + MovementMode: "BulkMoveTo" | "Motor6D", FastCastEventsModule: ModuleScript, useObjectCache: boolean, Template: BasePart | Model, @@ -181,14 +186,14 @@ export type Caster = { ) -> (), RaycastFire: ( - Caster, + CasterParallel, Origin: Vector3, Direction: Vector3, Velocity: Vector3 | number, Behavior: FastCastBehavior? ) -> (), BlockcastFire: ( - self: Caster, + self: CasterParallel, Origin: Vector3, Size: Vector3, Direction: Vector3, @@ -197,7 +202,7 @@ export type Caster = { ) -> (), SpherecastFire: ( - self: Caster, + self: CasterParallel, Origin: Vector3, Radius: number, Direction: Vector3, @@ -205,70 +210,122 @@ export type Caster = { Behavior: FastCastBehavior? ) -> (), - SetBulkMoveEnabled: (self: Caster, enabled: boolean) -> (), + SetMovementModeEnabled: ( + enabled: boolean, + mode: "BulkMoveTo" | "Motor6D" + ) -> (), + SetObjectCacheEnabled: ( - self: Caster, + self: CasterParallel, enabled: boolean, Template: BasePart | Model, CacheSize: number, CacheHolder: Instance ) -> (), - SetFastCastEventsModule: (self: Caster, moduleScript: ModuleScript) -> (), - - AddVelocityCast: (Caster, cast: vaildcast, velocity: Vector3) -> (), - SetVelocityCast: (Caster, cast: vaildcast, velocity: Vector3) -> (), - GetVelocityCast: (Caster, cast: vaildcast) -> Vector3, + SetFastCastEventsModule: (self: CasterParallel, moduleScript: ModuleScript) -> (), - AddAccelerationCast: (Caster, cast: vaildcast, acceleration: Vector3) -> Vector3, - SetAccelerationCast: (Caster, cast: vaildcast, acceleration: Vector3) -> (), - GetAccelerationCast: (Caster, cast: vaildcast) -> Vector3, + AddVelocityCast: (CasterParallel, cast: vaildcast, velocity: Vector3) -> (), + SetVelocityCast: (CasterParallel, cast: vaildcast, velocity: Vector3) -> (), + GetVelocityCast: (CasterParallel, cast: vaildcast) -> Vector3, - AddPositionCast: (Caster, cast: vaildcast, Position: Vector3) -> (), - GetPositionCast: (Caster, cast: vaildcast) -> Vector3, + AddAccelerationCast: (CasterParallel, cast: vaildcast, acceleration: Vector3) -> Vector3, + SetAccelerationCast: (CasterParallel, cast: vaildcast, acceleration: Vector3) -> (), + GetAccelerationCast: (CasterParallel, cast: vaildcast) -> Vector3, - ResumeCast: (Caster, cast: vaildcast) -> (), - PauseCast: (Caster, cast: vaildcast) -> (), + AddPositionCast: (CasterParallel, cast: vaildcast, Position: Vector3) -> (), + GetPositionCast: (CasterParallel, cast: vaildcast) -> Vector3, - SyncChangesToCast: (Caster, cast: vaildcast) -> (), + SyncChangesToCast: (CasterParallel, cast: vaildcast) -> (), - TerminateCast: (Caster, cast: vaildcast) -> (), + TerminateCast: (CasterParallel, cast: vaildcast) -> (), - Destroy: (Caster) -> () + Destroy: (CasterParallel) -> () } --[=[ - @type VisualizeCastSettings { Debug_SegmentColor: Color3, Debug_SegmentTransparency: number, Debug_SegmentSize: number, Debug_HitColor: Color3, Debug_HitTransparency: number, Debug_HitSize: number, Debug_RayPierceColor: Color3, Debug_RayPierceTransparency: number, Debug_RayPierceSize: number, Debug_RayLifetime: number, Debug_HitLifetime: number } + @type CasterSerial { WorldRoot: WorldRoot, LengthChanged: OnLengthChangedFunction, Hit: OnHitFunction, CanPierce: CanPierceFunction, Pierced: OnPiercedFunction, CastTerminating: OnCastTerminatingFunction, CastFire: OnCastFireFunction, Dispatcher: Dispatcher.Dispatcher, AlreadyInit: boolean, ObjectCacheEnabled: boolean, MovementMode: "BulkMoveTo" | "Motor6D", FastCastEventsModule: FastCastEventsModule, Init: ( self: CasterSerial, movementMode: "BulkMoveTo" | "Motor6D", useObjectCache: boolean, Template: BasePart | Model?, CacheSize: number?, CacheHolder: Instance? ) -> (), RaycastFire: ( CasterSerial, Origin: Vector3, Direction: Vector3, Velocity: Vector3 | number, Behavior: FastCastBehavior? ) -> (), BlockcastFire: ( self: CasterSerial, Origin: Vector3, Size: Vector3, Direction: Vector3, Velocity: Vector3 | number, Behavior: FastCastBehavior? ) -> (), SpherecastFire: ( self: CasterSerial, Origin: Vector3, Radius: number, Direction: Vector3, Velocity: Vector3 | number, Behavior: FastCastBehavior? ) -> (), SetMovementModeEnabled: ( mode: "BulkMoveTo" | "Motor6D", enabled: boolean ) -> (), SetObjectCacheEnabled: ( self: CasterSerial, enabled: boolean, Template: BasePart | Model, CacheSize: number, CacheHolder: Instance ) -> (), AddVelocityCast: (CasterSerial, cast: vaildcast, velocity: Vector3) -> (), SetVelocityCast: (CasterSerial, cast: vaildcast, velocity: Vector3) -> (), GetVelocityCast: (CasterSerial, cast: vaildcast) -> Vector3, AddAccelerationCast: (CasterSerial, cast: vaildcast, acceleration: Vector3) -> Vector3, SetAccelerationCast: (CasterSerial, cast: vaildcast, acceleration: Vector3) -> (), GetAccelerationCast: (CasterSerial, cast: vaildcast) -> Vector3, AddPositionCast: (CasterSerial, cast: vaildcast, Position: Vector3) -> (), GetPositionCast: (CasterSerial, cast: vaildcast) -> Vector3, TerminateCast: (CasterSerial, cast: vaildcast) -> (), Destroy: (CasterSerial) -> () } + @within TypeDefinitions - Debug visualization settings for casts. + Represents a Caster Serial. ]=] -export type VisualizeCastSettings = { - Debug_SegmentColor: Color3, - Debug_SegmentTransparency: number, - Debug_SegmentSize: number, +export type CasterSerial = { + WorldRoot: WorldRoot, + LengthChanged: OnLengthChangedFunction, + Hit: OnHitFunction, + CanPierce: CanPierceFunction, + Pierced: OnPiercedFunction, + CastTerminating: OnCastTerminatingFunction, + CastFire: OnCastFireFunction, + Dispatcher: Dispatcher.Dispatcher, - Debug_HitColor: Color3, - Debug_HitTransparency: number, - Debug_HitSize: number, + AlreadyInit: boolean, + ObjectCacheEnabled: boolean, + MovementMode: "BulkMoveTo" | "Motor6D", + FastCastEventsModule: FastCastEventsModule, - Debug_RayPierceColor: Color3, - Debug_RayPierceTransparency: number, - Debug_RayPierceSize: number, + Init: ( + self: CasterSerial, + movementMode: "BulkMoveTo" | "Motor6D", + useObjectCache: boolean, + Template: BasePart | Model?, + CacheSize: number?, + CacheHolder: Instance? + ) -> (), - Debug_RayLifetime: number, - Debug_HitLifetime: number, -} + RaycastFire: ( + CasterSerial, + Origin: Vector3, + Direction: Vector3, + Velocity: Vector3 | number, + Behavior: FastCastBehavior? + ) -> (), + BlockcastFire: ( + self: CasterSerial, + Origin: Vector3, + Size: Vector3, + Direction: Vector3, + Velocity: Vector3 | number, + Behavior: FastCastBehavior? + ) -> (), ---[=[ - @type AdaptivePerformance { HighFidelitySegmentSizeIncrease: number, LowerHighFidelityBehavior: boolean } - @within TypeDefinitions + SpherecastFire: ( + self: CasterSerial, + Origin: Vector3, + Radius: number, + Direction: Vector3, + Velocity: Vector3 | number, + Behavior: FastCastBehavior? + ) -> (), - Adaptive performance config used when AutomaticPerformance is enabled. -]=] -export type AdaptivePerformance = { - HighFidelitySegmentSizeIncrease: number, - LowerHighFidelityBehavior: boolean, + SetMovementModeEnabled: ( + enabled: boolean, + mode: "BulkMoveTo" | "Motor6D" + ) -> (), + + SetObjectCacheEnabled: ( + self: CasterSerial, + enabled: boolean, + Template: BasePart | Model, + CacheSize: number, + CacheHolder: Instance + ) -> (), + + AddVelocityCast: (CasterSerial, cast: vaildcast, velocity: Vector3) -> (), + SetVelocityCast: (CasterSerial, cast: vaildcast, velocity: Vector3) -> (), + GetVelocityCast: (CasterSerial, cast: vaildcast) -> Vector3, + + AddAccelerationCast: (CasterSerial, cast: vaildcast, acceleration: Vector3) -> Vector3, + SetAccelerationCast: (CasterSerial, cast: vaildcast, acceleration: Vector3) -> (), + GetAccelerationCast: (CasterSerial, cast: vaildcast) -> Vector3, + + AddPositionCast: (CasterSerial, cast: vaildcast, Position: Vector3) -> (), + GetPositionCast: (CasterSerial, cast: vaildcast) -> Vector3, + + TerminateCast: (CasterSerial, cast: vaildcast) -> (), + + Destroy: (CasterSerial) -> () } --[=[ @@ -297,11 +354,12 @@ export type FastCastEventsConfig = { UseHit: boolean, UsePierced: boolean, UseCastTerminating: boolean, - UseCastFire: boolean + UseCastFire: boolean, + UseCanPierce: boolean } --[=[ - @type FastCastBehavior { RaycastParams: RaycastParams?, MaxDistance: number, Acceleration: Vector3, HighFidelityBehavior: number, HighFidelitySegmentSize: number, CosmeticBulletTemplate: Instance?, CosmeticBulletContainer: Instance?, AutoIgnoreContainer: boolean, SimulateAfterPhysic: boolean, AutomaticPerformance: boolean, AdaptivePerformance: AdaptivePerformance, VisualizeCasts: boolean, VisualizeCastSettings: VisualizeCastSettings, FastCastEventsModuleConfig: FastCastEventsModuleConfig, FastCastEventsConfig: FastCastEventsConfig, UserData: any } + @type FastCastBehavior { RaycastParams: RaycastParams?, MaxDistance: number, Acceleration: Vector3, HighFidelityBehavior: number, HighFidelitySegmentSize: number, CosmeticBulletTemplate: Instance?, CosmeticBulletContainer: Instance?, AutoIgnoreContainer: boolean, MovementMethod: "BulkMoveTo" | "Transform", FastCastEventsModuleConfig: FastCastEventsModuleConfig, VisualizeCasts: boolean, VisualizeCastSettings: VisualizeCastSettings, FastCastEventsConfig: FastCastEventsConfig, UserData: any } @within TypeDefinitions Represents a FastCastBehavior configuration. @@ -316,15 +374,9 @@ export type FastCastBehavior = { CosmeticBulletContainer: Instance?, AutoIgnoreContainer: boolean, - SimulateAfterPhysic: boolean, - - AutomaticPerformance: boolean, - AdaptivePerformance: AdaptivePerformance, - - VisualizeCasts: boolean, - VisualizeCastSettings: VisualizeCastSettings, - FastCastEventsModuleConfig: FastCastEventsModuleConfig, + VisualizeCasts: boolean, + VisualizeCastSettings: VisualizeCastSettings, FastCastEventsConfig: FastCastEventsConfig, UserData: any @@ -345,23 +397,21 @@ export type CastTrajectory = { } --[=[ - @type CastStateInfo { UpdateConnection: RBXScriptSignal, HighFidelityBehavior: number, HighFidelitySegmentSize: number, Paused: boolean, TotalRuntime: number, DistanceCovered: number, IsActivelySimulatingPierce: boolean, IsActivelyResimulating: boolean, CancelHighResCast: boolean, Trajectories: { [number]: CastTrajectory }, VisualizeCasts: boolean, VisualizeCastSettings: VisualizeCastSettings, FastCastEventsConfig: FastCastEventsConfig, FastCastEventsModuleConfig: FastCastEventsModuleConfig } + @type CastStateInfo { HighFidelityBehavior: number, HighFidelitySegmentSize: number, TotalRuntime: number, DistanceCovered: number, IsActivelySimulatingPierce: boolean, IsActivelyResimulating: boolean, CancelHighResCast: boolean, Trajectory: CastTrajectory, VisualizeCasts: boolean, VisualizeCastSettings: VisualizeCastSettings, FastCastEventsConfig: FastCastEventsConfig, FastCastEventsModuleConfig: FastCastEventsModuleConfig } @within TypeDefinitions Represents cast state tracking data. ]=] export type CastStateInfo = { - UpdateConnection: RBXScriptConnection?, HighFidelityBehavior: number, HighFidelitySegmentSize: number, - Paused: boolean, TotalRuntime: number, DistanceCovered: number, IsActivelySimulatingPierce: boolean, IsActivelyResimulating: boolean, CancelHighResCast: boolean, - Trajectories: { [number]: CastTrajectory }, - VisualizeCasts: boolean, + Trajectory: CastTrajectory, + VisualizeCasts: boolean, -- I don't use this anymore VisualizeCastSettings: VisualizeCastSettings, FastCastEventsConfig: FastCastEventsConfig, @@ -370,7 +420,7 @@ export type CastStateInfo = { } --[=[ - @type CastRayInfo { Parameters: RaycastParams, WorldRoot: WorldRoot, MaxDistance: number, CosmeticBulletObject: Instance?, CanPierceModule: ModuleScript? } + @type CastRayInfo { Parameters: RaycastParams, WorldRoot: WorldRoot, MaxDistance: number, CosmeticBulletObject: Instance?, FastCastEventsModule: FastCastEventsModule } @within TypeDefinitions Ray info for ray-cast variants. @@ -422,21 +472,18 @@ export type SphereCastRayInfo = { export type BaseCastData = { Output: BindableEvent, ActiveCastCleaner: BindableEvent, - ObjectCache: BindableFunction?, CacheHolder: any?, SyncChange : BindableEvent } --- ECS - --[=[ - @type ActiveCastData {Caster: BaseCastData,StateInfo: CastStateInfo,RayInfo: CastRayInfo,UserData: { [any]: any }, Type : "Raycast",CFrame: CFrame,ID: number} + @type ActiveCastData {Caster: BaseCastData | { SerialSimulation: any},StateInfo: CastStateInfo,RayInfo: CastRayInfo,UserData: { [any]: any }, Type : "Raycast",CFrame: CFrame,ID: number} @within TypeDefinitions Represents an active cast data. ]=] export type ActiveCastData = { - Caster: BaseCastData, + Caster: BaseCastData | { SerialSimulation: any}, StateInfo: CastStateInfo, RayInfo: CastRayInfo, UserData: { [any]: any }, @@ -447,13 +494,13 @@ export type ActiveCastData = { } --[=[ - @type ActiveCastData { Caster: BaseCastData, StateInfo: CastStateInfo, RayInfo: CastRayInfo, UserData: { [any]: any }, Type : "Blockcast", CFrame: CFrame, ID: number } + @type ActiveCastData { Caster: BaseCastData | { SerialSimulation: any}, StateInfo: CastStateInfo, RayInfo: CastRayInfo, UserData: { [any]: any }, Type : "Blockcast", CFrame: CFrame, ID: number } @within TypeDefinitions Represents an active block cast data. ]=] export type ActiveBlockcastData = { - Caster: BaseCastData, + Caster: BaseCastData | { SerialSimulation: any}, StateInfo: CastStateInfo, RayInfo: BlockCastRayInfo, UserData: { [any]: any }, @@ -464,13 +511,13 @@ export type ActiveBlockcastData = { } --[=[ - @type ActiveCastData { Caster: BaseCastData, StateInfo: CastStateInfo, RayInfo: CastRayInfo, UserData: { [any]: any }, Type : "Spherecast", CFrame: CFrame, ID: number } + @type ActiveCastData { Caster: BaseCastData | { SerialSimulation: any}, StateInfo: CastStateInfo, RayInfo: CastRayInfo, UserData: { [any]: any }, Type : "Spherecast", CFrame: CFrame, ID: number } @within TypeDefinitions Represents an active sphere cast data. ]=] export type ActiveSpherecastData = { - Caster: BaseCastData, + Caster: BaseCastData | { SerialSimulation: any}, StateInfo: CastStateInfo, RayInfo: SphereCastRayInfo, UserData: { [any]: any }, diff --git a/src/FastCast2/init.luau b/src/init.luau similarity index 58% rename from src/FastCast2/init.luau rename to src/init.luau index 44dbca09..d95df726 100644 --- a/src/FastCast2/init.luau +++ b/src/init.luau @@ -13,7 +13,7 @@ YOU SHOULD ONLY CREATE ONE CASTER PER GUN. YOU SHOULD >>>NEVER<<< CREATE A NEW CASTER EVERY TIME THE GUN NEEDS TO BE FIRED. - A caster (created with FastCast.new()) represents a "gun". + A caster (created with FastCast.new() or FastCastParallel.new()) represents a "gun". When you consider a gun, you think of stats like accuracy, bullet speed, etc. This is the info a caster stores. -- @@ -31,7 +31,7 @@ Unfortunately, while reliable in terms of saying if something got hit or not, this method alone cannot be used if you wish to implement bullet travel time into a weapon. As a result of that, I made this library - an excellent remedy to this dilemma. - FastCast is intended to be require()'d once in a script, as you can create as many casters as you need with FastCast.new() + FastCastParallel is intended to be require()'d once in a script, as you can create as many casters as you need with FastCastParallel.new() This is generally handy since you can store settings and information in these casters, and even send them out to other scripts via events for use. @@ -39,19 +39,19 @@ Make the caster once, then use the caster to fire your bullets. Do not make a caster for each bullet. --]] --- Mozilla Public License 2.0 (files originally from FastCast) +-- Mozilla Public License 2.0 (files originally from FastCastParallel) --[[ - Modified by: Mawin CK - Date : 2025 ]] --- Verison : 0.0.9 + --[=[ - @class FastCast + @class FastCastParallel - FastCast is the root class of the module and offers the surface level methods required to make it work. This is the object returned from `require(FastCast)`. + FastCastParallel is the root class of the module and offers the surface level methods required to make it work. This is the object returned from `require(FastCastParallel)`. ]=] -- Services @@ -63,14 +63,9 @@ --local BaseCast = script:WaitForChild("BaseCast") -- Requires -local Signal = require(script:WaitForChild("Signal")) local TypeDef = require(script:WaitForChild("TypeDefinitions")) local DefaultConfigs = require(script:WaitForChild("DefaultConfigs")) ---local Configs = require(script:WaitForChild("Configs")) -local ObjectCache = require(script:WaitForChild("ObjectCache")) - ---local SharedCasters = require(script:WaitForChild("SharedCasters")) - +local BaseCastSerial = require(script:WaitForChild("BaseCastSerial")) local DispatcherModule = script:WaitForChild("FastCastVMs") local Dispatcher = require(DispatcherModule) @@ -80,28 +75,58 @@ type vaildcast = TypeDef.ActiveCastData | TypeDef.ActiveBlockcastData | TypeDef. -- CONSTANTS local DEFAULT_CACHE_SIZE = 500 local DEFAULT_CACHE_HOLDER = workspace +local VALID_EVENTS = { + ["CastFire"] = true, + ["CastTerminating"] = true, + ["Hit"] = true, + ["Pierced"] = true, + ["LengthChanged"] = true, + ["CanPierce"] = true +} -- FastCast local FastCast = {} +FastCast.HighFidelityBehavior = { + Default = 1, + Automatic = 2, + Always = 3 +} +FastCast.CastType = { + Raycast = 1, + Blockcast = 2, + Spherecast = 3 +} +local FastCastSerial = {} +local FastCastParallel = {} --[[ If true, verbose debug logging will be used, printing detailed information about what's going on during processing to the output. ]] -FastCast.__index = FastCast -FastCast.__type = "FastCast" - --- Local functions - -local function DestroySignal(signal: Signal.Signal?) - if type(signal) == "table" then - signal:Destroy() +FastCastSerial.__index = FastCastSerial +FastCastSerial.__newindex = function(self, key, value) + if VALID_EVENTS[key] then + if type(value) == "function" then + if self.BaseCast then + self.BaseCast:_UpdateEvents(key, value) + else + rawset(self, key, value) + end + else + warn("Cannot set event, not a function") + end else - signal = nil + rawset(self, key, value) end end +FastCastSerial.__type = "FastCastSerial" + +FastCastParallel.__index = FastCastParallel +FastCastParallel.__type = "FastCastParallel" + +-- Local functions local function GetPositionAtTime( time: number, @@ -118,14 +143,15 @@ local function GetVelocityAtTime(time: number, initialVelocity: Vector3, acceler return initialVelocity + acceleration * time end +--[[ local function GetTrajectoryInfo( cast: vaildcast, index: number ): { [number]: Vector3 } - assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED") - local trajectories = cast.StateInfo.Trajectories - local trajectory = trajectories[index] - local duration = trajectory.EndTime - trajectory.StartTime + local trajectory = cast.StateInfo.Trajectory + local duration = trajectory.EndTime ~= -1 + and (trajectory.EndTime - trajectory.StartTime) + or (cast.StateInfo.TotalRuntime - trajectory.StartTime) local origin = trajectory.Origin local vel = trajectory.InitialVelocity @@ -133,10 +159,13 @@ local function GetTrajectoryInfo( return { GetPositionAtTime(duration, origin, vel, accel), GetVelocityAtTime(duration, vel, accel) } end +--]] +--[[ local function GetLatestTrajectoryEndInfo(cast: vaildcast): { [number]: Vector3 } - return GetTrajectoryInfo(cast, #cast.StateInfo.Trajectories) + return GetTrajectoryInfo(cast, 1) end +--]] local function ModifyTransformation( cast: vaildcast, @@ -144,46 +173,17 @@ local function ModifyTransformation( acceleration: Vector3?, position: Vector3? ) - local trajectories = cast.StateInfo.Trajectories - local lastTrajectory = trajectories[#trajectories] + local trajectory = cast.StateInfo.Trajectory - if lastTrajectory.StartTime == cast.StateInfo.TotalRuntime then - if velocity == nil then - velocity = lastTrajectory.InitialVelocity - end - if acceleration == nil then - acceleration = lastTrajectory.Acceleration - end - if position == nil then - position = lastTrajectory.Origin - end + local t = cast.StateInfo.TotalRuntime - trajectory.StartTime + local currentPosition = GetPositionAtTime(t, trajectory.Origin, trajectory.InitialVelocity, trajectory.Acceleration) + local currentVelocity = GetVelocityAtTime(t, trajectory.InitialVelocity, trajectory.Acceleration) - lastTrajectory.Origin = position - lastTrajectory.InitialVelocity = velocity - lastTrajectory.Acceleration = acceleration - else - lastTrajectory.EndTime = cast.StateInfo.TotalRuntime - - local point, velAtPoint = unpack(GetLatestTrajectoryEndInfo(cast)) - - if velocity == nil then - velocity = velAtPoint - end - if acceleration == nil then - acceleration = lastTrajectory.Acceleration - end - if position == nil then - position = point - end - table.insert(cast.StateInfo.Trajectories, { - StartTime = cast.StateInfo.TotalRuntime, - EndTime = -1, - Origin = position, - InitialVelocity = velocity, - Acceleration = acceleration, - }) - cast.StateInfo.CancelHighResCast = true - end + trajectory.Origin = position or currentPosition + trajectory.InitialVelocity = velocity or currentVelocity + trajectory.Acceleration = acceleration or trajectory.Acceleration + trajectory.StartTime = cast.StateInfo.TotalRuntime + cast.StateInfo.CancelHighResCast = true end local function deepCopyTable(tbl: {any}): {any} @@ -207,38 +207,10 @@ function FastCast.newBehavior(): TypeDef.FastCastBehavior return deepCopyTable(DefaultConfigs.FastCastBehavior) :: TypeDef.FastCastBehavior end - ---[=[ - :::warning - - You must [initialize](FastCast#Init) the Caster before using it. Failing to do so will result in nothing happening when attempting to fire! - - ::: - Contructs a new Caster object. - @function new - @within FastCast - @return Caster -]=] -function FastCast.new(): TypeDef.Caster - return setmetatable( - { - LengthChanged = Signal.new(), - Hit = Signal.new(), - Pierced = Signal.new(), - CastTerminating = Signal.new(), - CastFire = Signal.new(), - WorldRoot = workspace, - Dispatcher = nil, - AlreadyInit = false, - } :: any, - FastCast - ) :: TypeDef.Caster -end - --[=[ Initializes the Caster with the given parameters. This is required before firing using Raycasts in the Caster or nothing will happen! @method Init - @within FastCast + @within FastCastParallel @param numWorkers number -- The number of worker VMs to create for this Caster. Must be greater than 1. @param newParent Folder -- The Folder in which to place the FastCastVMs Folder @@ -253,7 +225,7 @@ end @param CacheSize number -- The size of the ObjectCache (if enabled) @param CacheHolder Instance -- The Instance in which to place cached objects (if enabled) ]=] -function FastCast:Init( +function FastCastParallel:Init( numWorkers: number, newParent: Folder, newName: string, @@ -261,9 +233,9 @@ function FastCast:Init( VMContainerName: string, VMname: string, - useBulkMoveTo: boolean, + movementMode: "BulkMoveTo" | "Motor6D", FastCastEventsModule: ModuleScript, - + useObjectCache: boolean, Template: BasePart | Model, CacheSize: number, @@ -273,7 +245,7 @@ function FastCast:Init( warn("Cannot Init more than 1") return end - assert(numWorkers >= 1, "numWorker must be more than 1") + assert(numWorkers > 1, "numWorker must be more than 1") local DispatcherClone = DispatcherModule:Clone() DispatcherClone.Parent = newParent @@ -282,22 +254,15 @@ function FastCast:Init( local newDispatcher: Dispatcher.Dispatcher = require(DispatcherClone) :: Dispatcher.Dispatcher newDispatcher.Init(ContainerParent, VMContainerName, VMname) - - if useObjectCache then - if not CacheSize then - CacheSize = DEFAULT_CACHE_SIZE - end - - if not CacheHolder then - CacheHolder = DEFAULT_CACHE_HOLDER - end - - self.ObjectCache = ObjectCache.new(Template, CacheSize, CacheHolder) :: any - end local data = { - useBulkMoveTo = useBulkMoveTo, - useObjectCache = useObjectCache + movementMode = movementMode, + useObjectCache = useObjectCache, + objectCacheArgs = { + Template = Template, + CacheSize = CacheSize, + CacheHolder = CacheHolder + } } self.Dispatcher = newDispatcher.new(numWorkers, data, function(signalName: string, ...) local f = self[signalName] @@ -307,58 +272,32 @@ function FastCast:Init( if type(f) == "function" then f(...) - else - f:Fire(...) end end) self.AlreadyInit = true self.ObjectCacheEnabled = useObjectCache - self.BulkMoveEnabled = useBulkMoveTo - + self.MovementMode = movementMode + if FastCastEventsModule then self:SetFastCastEventsModule(FastCastEventsModule) end - - - if not useObjectCache then - return - end - - local vmDispatcher = self.Dispatcher - - repeat - task.wait() - until #vmDispatcher.Threads == numWorkers - --print("STARTED CONNECTING") - - for _, v in vmDispatcher.Threads do - -- Please dont change this to FindFirstChild, or else diddy will oil you up - local BindableObjectCache: BindableFunction = v:WaitForChild("ActiveCastObjectCache") :: BindableFunction - if BindableObjectCache then - --print("CONNECTED") - BindableObjectCache.OnInvoke = function(targetCFrame: CFrame) - --print("INVOKED") - return self.ObjectCache:GetPart(targetCFrame) - end -- OH MY GOD - end -- KEEP GOING - end -- OH YES DADDY end --[=[ Set the FastCastEventsModule for all BaseCasts created from this Caster. @method SetFastCastEventsModule - @within FastCast + @within FastCastParallel @param moduleScript ModuleScript -- The FastCastEventsModule to set. ]=] -function FastCast:SetFastCastEventsModule(moduleScript: ModuleScript) +function FastCastParallel:SetFastCastEventsModule(moduleScript: ModuleScript) if not self.AlreadyInit then error("Please Init caster") end - + self.Dispatcher:DispatchAll("SetFastCastEventsModule", moduleScript) self.FastCastEventsModule = moduleScript end @@ -366,14 +305,14 @@ end --[=[ Raycasts the Caster with the specified parameters. @method RaycastFire - @within FastCast + @within FastCastParallel @param origin Vector3 -- The origin of the raycast. @param direction Vector3 -- The direction of the raycast. @param velocity Vector3 | number -- The velocity of the raycast. @param BehaviorData FastCastBehavior? -- The behavior data for the raycast. ]=] -function FastCast:RaycastFire( +function FastCastParallel:RaycastFire( origin: Vector3, direction: Vector3, velocity: Vector3 | number, @@ -386,14 +325,13 @@ function FastCast:RaycastFire( BehaviorData = FastCast.newBehavior() end - -- BABE RAYCAST!!!!! self.Dispatcher:Dispatch("Raycast", origin, direction, velocity, BehaviorData) end --[=[ Blockcasts the Caster with the specified parameters. @method BlockcastFire - @within FastCast + @within FastCastParallel @param origin Vector3 -- The origin of the blockcast. @param Size Vector3 -- The size of the blockcast. @@ -401,7 +339,7 @@ end @param velocity Vector3 | number -- The velocity of the blockcast. @param BehaviorData FastCastBehavior? -- The behavior data for the blockcast. ]=] -function FastCast:BlockcastFire( +function FastCastParallel:BlockcastFire( origin: Vector3, Size: Vector3, direction: Vector3, @@ -421,7 +359,7 @@ end --[=[ Spherecasts the Caster with the specified parameters. @method SpherecastFire - @within FastCast + @within FastCastParallel @param origin Vector3 -- The origin of the spherecast. @param Radius number -- The radius of the spherecast. @@ -429,7 +367,7 @@ end @param velocity Vector3 | number -- The velocity of the spherecast. @param BehaviorData FastCastBehavior? -- The behavior data for the spherecast. ]=] -function FastCast:SpherecastFire( +function FastCastParallel:SpherecastFire( origin: Vector3, Radius: number, direction: Vector3, @@ -442,10 +380,253 @@ function FastCast:SpherecastFire( if BehaviorData == nil then BehaviorData = FastCast.newBehavior() end - + self.Dispatcher:Dispatch("Spherecast", origin, Radius, direction, velocity, BehaviorData) end +--[=[ + Sets the movement mode for casts. + + @method SetMovementMode + @param enabled boolean -- Is enabled + @param mode "BulkMoveTo" | "Motor6D" -- The movement mode to set for casts. + @within FastCastParallel +]=] +function FastCastParallel:SetMovementMode(enabled: boolean, mode: "BulkMoveTo" | "Motor6D") + if not self.AlreadyInit or not self.Dispatcher then + warn("Caster not initialized", self) + return + end + + self.Dispatcher:DispatchAll("SetMovementModeEnabled", enabled, mode) + self.MovementMode = mode +end + +--[=[ + Sets whether ObjectCache is enabled for this Caster. + It is recommended to interface with this via [`FastCastParallel:Init()`](FastCastParallel#Init) instead. + @method SetObjectCacheEnabled + @within FastCastParallel + + @param enabled boolean +]=] +function FastCastParallel:SetObjectCacheEnabled( + enabled: boolean, + Template: BasePart | Model, + CacheSize: number, + CacheHolder: Instance +) + if not self.AlreadyInit then + error("Please Init caster") + end + local vmDispatcher = self.Dispatcher + + if enabled then + vmDispatcher:DispatchAll("BindObjectCache", enabled, Template, CacheSize, CacheHolder) + else + vmDispatcher:DispatchAll("BindObjectCache", enabled) + end + + self.ObjectCacheEnabled = enabled +end + +-- Serial Caster Methods + +--[=[ + Initialize the Serial Caster. + @method Init + @within FastCastSerial + + @param useBulkMoveTo boolean -- Whether to use BulkMoveTo for projectile movement. + @param useObjectCache boolean -- Whether to use ObjectCache. + @param Template BasePart | Model? -- Template for ObjectCache. + @param CacheSize number? -- Size of ObjectCache. + @param CacheHolder Instance? -- Parent for cached objects. +]=] +function FastCastSerial:Init( + movementMode: "BulkMoveTo" | "Motor6D", + useObjectCache: boolean, + Template: BasePart | Model?, + CacheSize: number?, + CacheHolder: Instance? +) + if self.BaseCast then + warn("Serial Caster already initialized") + return + end + + local data = { + movementMode = movementMode or "BulkMoveTo", + useObjectCache = useObjectCache, + objectCacheArgs = { + Template = Template, + CacheSize = CacheSize or DEFAULT_CACHE_SIZE, + CacheHolder = CacheHolder or DEFAULT_CACHE_HOLDER + } + } + + local events: TypeDef.FastCastEvents = { + CastFire = self.CastFire, + Pierced = self.Pierced, + Hit = self.Hit, + LengthChanged = self.LengthChanged, + CanPierce = self.CanPierce, + CastTerminating = self.CastTerminating + } + + self.BaseCast = BaseCastSerial.Init(events, data) + + self.MovementMode = movementMode or "BulkMoveTo" + self.AlreadyInit = true +end + +--[=[ + @method RaycastFire + @within FastCastSerial +]=] +function FastCastSerial:RaycastFire( + origin: Vector3, + direction: Vector3, + velocity: Vector3 | number, + BehaviorData: TypeDef.FastCastBehavior? +) + if not self.AlreadyInit then + error("Please Init caster first") + end + if BehaviorData == nil then + BehaviorData = FastCast.newBehavior() + end + + self.BaseCast:Raycast(origin, direction, velocity, BehaviorData) +end + +--[=[ + @method BlockcastFire + @within FastCastSerial +]=] +function FastCastSerial:BlockcastFire( + origin: Vector3, + Size: Vector3, + direction: Vector3, + velocity: Vector3 | number, + BehaviorData: TypeDef.FastCastBehavior? +) + if not self.AlreadyInit then + error("Please Init caster first") + end + if BehaviorData == nil then + BehaviorData = FastCast.newBehavior() + end + + self.BaseCast:Blockcast(origin, Size, direction, velocity, BehaviorData) +end + +--[=[ + @method SpherecastFire + @within FastCastSerial +]=] +function FastCastSerial:SpherecastFire( + origin: Vector3, + Radius: number, + direction: Vector3, + velocity: Vector3 | number, + BehaviorData: TypeDef.FastCastBehavior? +) + if not self.AlreadyInit then + error("Please Init caster first") + end + if BehaviorData == nil then + BehaviorData = FastCast.newBehavior() + end + + self.BaseCast:Spherecast(origin, Radius, direction, velocity, BehaviorData) +end + +--[[ + @method SetMovementMode + @param enabled boolean -- Is enabled + @param mode "BulkMoveTo" | "Motor6D" -- MovementMode + + @within FastCastSerial + Sets movement mode for the Serial Caster. +]] +function FastCastSerial:SetMovementModeEnabled(enabled: boolean, mode: "BulkMoveTo" | "Motor6D") + if not self.BaseCast then return end + + self.BaseCast:SetMovementModeEnabled(enabled, mode) + self.MovementMode = mode +end + +--[=[ + @method SetObjectCacheEnabled + @within FastCastSerial +]=] +function FastCastSerial:SetObjectCacheEnabled( + enabled: boolean, + Template: BasePart | Model, + CacheSize: number, + CacheHolder: Instance +) + if not self.BaseCast then return end + + self.BaseCast:BindObjectCache( + enabled, + Template, + CacheSize, + CacheHolder + ) + self.ObjectCacheEnabled = enabled +end + +--[=[ + @method Destroy + @within FastCastSerial +]=] +function FastCastSerial:Destroy() + if getmetatable(self) ~= FastCastSerial then + return + end + + if self.BaseCast then + self.BaseCast:Destroy() + end + + self.LengthChanged = nil + self.Hit = nil + self.Pierced = nil + self.CanPierce = nil + self.CastTerminating = nil + self.CastFire = nil + + self.Destroy = function() end + setmetatable(self, nil) +end + +--[=[ + Destroy's a Caster, cleaning up all resources used by it. + @method Destroy + @within FastCastParallel +]=] +function FastCastParallel:Destroy() + if getmetatable(self) ~= FastCastParallel then + return + end + + self.LengthChanged = nil + self.Hit = nil + self.Pierced = nil + self.CastTerminating = nil + self.CastFire = nil + + if self.Dispatcher then + self.Dispatcher:Destroy() + end + self.Destroy = function() end + setmetatable(self, nil) +end + +-- Utility Methods + --[=[ Gets the velocity of an ActiveCast. @@ -456,8 +637,7 @@ Gets the velocity of an ActiveCast. @return Vector3 -- The current velocity of the ActiveCast. ]=] function FastCast:GetVelocityCast(cast: vaildcast) - assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED") - local currentTrajectory = cast.StateInfo.Trajectories[#cast.StateInfo.Trajectories] + local currentTrajectory = cast.StateInfo.Trajectory return GetVelocityAtTime( cast.StateInfo.TotalRuntime - currentTrajectory.StartTime, currentTrajectory.InitialVelocity, @@ -476,9 +656,7 @@ Gets the acceleration of an ActiveCast. ]=] function FastCast:GetAccelerationCast(cast: vaildcast) - assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED") - local currentTrajectory = cast.StateInfo.Trajectories[#cast.StateInfo.Trajectories] - return currentTrajectory.Acceleration + return cast.StateInfo.Trajectory.Acceleration end --[=[ @@ -491,8 +669,7 @@ Gets the position of an ActiveCast. @return Vector3 -- The current position of the ActiveCast. ]=] function FastCast:GetPositionCast(cast: vaildcast) - assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED") - local currentTrajectory = cast.StateInfo.Trajectories[#cast.StateInfo.Trajectories] + local currentTrajectory = cast.StateInfo.Trajectory return GetPositionAtTime( cast.StateInfo.TotalRuntime - currentTrajectory.StartTime, currentTrajectory.Origin, @@ -512,7 +689,6 @@ Sets the velocity of an ActiveCast to the specified Vector3. ]=] function FastCast:SetVelocityCast(cast: vaildcast, velocity: Vector3) - assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED") ModifyTransformation(cast, velocity, nil, nil) end @@ -527,7 +703,6 @@ Sets the acceleration of an ActiveCast to the specified Vector3. ]=] function FastCast:SetAccelerationCast(cast: vaildcast, acceleration: Vector3) - assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED") ModifyTransformation(cast, nil, acceleration, nil) end @@ -540,23 +715,7 @@ end @within FastCast ]=] function FastCast:SetPositionCast(cast: vaildcast, position: Vector3) - assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED") - ModifyTransformation(cast, nil, nil, position) -end - ---[=[ - -Pauses or resumes simulation for an ActiveCast. - - @method PauseCast - @param cast vaildcast -- The active cast to modify. - @param value boolean -- Whether to pause (true) or resume (false) the cast. - @within FastCast - -]=] -function FastCast:PauseCast(cast: vaildcast, value: boolean) - assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED") - cast.StateInfo.Paused = value + ModifyTransformation(cast, nil, nil, position) end --[=[ @@ -570,8 +729,7 @@ Add position to an ActiveCast with the specified Vector3. ]=] function FastCast:AddPositionCast(cast: vaildcast, position: Vector3) - assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED") - self:SetPositionCast(cast, self:GetPositionCast(cast) + position) + FastCast:SetPositionCast(cast, FastCast:GetPositionCast(cast) + position) end --[=[ @@ -585,8 +743,7 @@ Add velocity to an ActiveCast with the specified Vector3. ]=] function FastCast:AddVelocityCast(cast: vaildcast, velocity: Vector3) - assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED") - self:SetVelocityCast(cast, self:GetVelocityCast(cast) + velocity) + FastCast:SetVelocityCast(cast, FastCast:GetVelocityCast(cast) + velocity) end --[=[ @@ -600,8 +757,7 @@ Add acceleration to an ActiveCast with the specified Vector3. ]=] function FastCast:AddAccelerationCast(cast: vaildcast, acceleration: Vector3) - assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED") - self:SetAccelerationCast(cast, self:GetAccelerationCast(cast) + acceleration) + FastCast:SetAccelerationCast(cast, FastCast:GetAccelerationCast(cast) + acceleration) end --[=[ @@ -610,12 +766,13 @@ Synchronize new changes to the ActiveCast. @method SyncChangesToCast @param cast vaildcast -- The active cast to synchronize. - @within FastCast + @within FastCastParallel ]=] -function FastCast:SyncChangesToCast(cast: vaildcast) - assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED") - cast.Caster.SyncChange:Fire(cast) +function FastCastParallel:SyncChangesToCast(cast: vaildcast) + local syncChange: BindableEvent = (cast.Caster :: any).SyncChange + + syncChange:Fire(cast) end --[=[ @@ -624,114 +781,83 @@ end @param cast vaildcast -- The active cast to terminate. @param castTerminatingFunction (cast: vaildcast) -> ())? -- Optional callback invoked just before the cast is terminated. @within FastCast -]=] -function FastCast:TerminateCast(cast: vaildcast, castTerminatingFunction: TypeDef.OnCastTerminatingFunction?) - assert(cast.StateInfo.UpdateConnection ~= nil, "ERR_OBJECT_DISPOSED") - local trajectories = cast.StateInfo.Trajectories - local lastTrajectory = trajectories[#trajectories] - lastTrajectory.EndTime = cast.StateInfo.TotalRuntime + Note: If EndTime is already set, the cast is already terminated and this function returns early. +]=] +function FastCast:TerminateCast(cast: vaildcast) + local caster = cast.Caster + if caster == nil then return end + local eventsCfg = cast.StateInfo and cast.StateInfo.FastCastEventsConfig - if cast.StateInfo.UpdateConnection then - cast.StateInfo.UpdateConnection:Disconnect() - cast.StateInfo.UpdateConnection = nil - end - - local FastCastEventsConfig = cast.StateInfo.FastCastEventsConfig - if FastCastEventsConfig and FastCastEventsConfig.UseCastTerminating then - cast.Caster.Output:Fire("CastTerminating", cast) - end - - if castTerminatingFunction then - castTerminatingFunction((cast :: any)) + if caster.Output then + -- Parallel mode + if eventsCfg and eventsCfg.UseCastTerminating then + caster.Output:Fire("CastTerminating", cast) + end + caster.ActiveCastCleaner:Fire(cast.ID) + elseif caster.SerialSimulation then + -- Serial mode + caster.SerialSimulation:Unregister(cast.ID) + caster.Actives[cast.ID] = nil end - - cast.Caster.ActiveCastCleaner:Fire(cast.ID) for key, _ in (cast :: any) do cast[key] = nil end end +-- Constructors + --[=[ - Sets whether BulkMoveTo is enabled for this Caster. - @method SetBulkMoveEnabled + Creates a new Serial Caster. A Serial Caster runs all cast simulations on the main thread + and is simpler to use but less performant than [FastCast.newParallel](FastCast#newParallel). + + @function new @within FastCast - @param enabled boolean + @return Caster ]=] -function FastCast:SetBulkMoveEnabled(enabled: boolean) - if not self.AlreadyInit or not self.Dispatcher then - warn("Caster not initialized", self) - end - - self.Dispatcher:DispatchAll("BindBulkMoveTo", enabled) - self.BulkMoveEnabled = enabled +function FastCast.new() + local fs = { + LengthChanged = nil, + Hit = nil, + Pierced = nil, + CanPierce = nil, + CastTerminating = nil, + CastFire = nil, + WorldRoot = workspace, + } + setmetatable(fs, FastCastSerial) + return fs end --[=[ - Sets whether ObjectCache is enabled for this Caster. - It is recommended to interface with this via [`FastCast:Init()`](FastCast#Init) instead. - @method SetObjectCacheEnabled - @within FastCast - - @param enabled boolean -]=] -function FastCast:SetObjectCacheEnabled( - enabled: boolean, - Template: BasePart | Model, - CacheSize: number, - CacheHolder: Instance -) - if not self.AlreadyInit then - error("Please Init caster") - end - local vmDispatcher = self.Dispatcher - - if enabled then - self.ObjectCache = ObjectCache.new(Template, CacheSize, CacheHolder) - vmDispatcher:DispatchAll("BindObjectCache", enabled) + Creates a new Parallel Caster. A Parallel Caster runs cast simulations on separate worker VMs - for _, v in vmDispatcher.Threads do - local BindableObjectCache: BindableFunction = v:WaitForChild("ActiveCastObjectCache") :: BindableFunction - if BindableObjectCache then - BindableObjectCache.OnInvoke = function(targetCFrame: CFrame) - return self.ObjectCache:GetPart(targetCFrame) - end - end - end - else - vmDispatcher:DispatchAll("BindObjectCache", enabled) - if self.ObjectCache then - self.ObjectCache:Destroy() - self.ObjectCache = nil - end - end - - self.ObjectCacheEnabled = enabled -end + :::warning + You must [initialize](FastCastParallel#Init) the Parallel Caster before using it! + Failing to do so will result in nothing happening when attempting to fire! + ::: ---[=[ - Destroy's a Caster, cleaning up all resources used by it. - @method Destroy + @function newParallel @within FastCast -]=] -function FastCast:Destroy() - if self.ObjectCache then - self.ObjectCache:Destroy() - end - -- I'm making sure that everything is destroyed here lmao - DestroySignal(self.LengthChanged) - DestroySignal(self.Hit) - DestroySignal(self.Pierced) - DestroySignal(self.CastTerminating) - DestroySignal(self.CastFire) - - self.Dispatcher:Destroy() - setmetatable(self, nil) + @return Caster +]=] +function FastCast.newParallel() + local fp = { + LengthChanged = nil, + Hit = nil, + Pierced = nil, + CastTerminating = nil, + CastFire = nil, + WorldRoot = workspace, + Dispatcher = nil, + AlreadyInit = false + } + setmetatable(fp, FastCastParallel) + return fp end - -return FastCast \ No newline at end of file +return FastCast diff --git a/test.project.json b/test.project.json new file mode 100644 index 00000000..0a4945b5 --- /dev/null +++ b/test.project.json @@ -0,0 +1,17 @@ +{ + "name": "FastCast2-Tests", + "tree": { + "$className": "DataModel", + "ReplicatedFirst": { + "FastCast2_Tests": { + "$path": "tests" + } + }, + "ReplicatedStorage": { + "FastCast2": { + "$path": "src" + }, + "$ignoreUnknownInstances": true + } + } +} diff --git a/wally.toml b/wally.toml index 43b75861..464d12f2 100644 --- a/wally.toml +++ b/wally.toml @@ -2,6 +2,6 @@ name = "weenachuangkud/fastcast2" # Replace with your Roblox username and project name description = "An improved version of FastCast with Parallel scripting, more extensions, and statically typed" author = ["Mawin Chuangkud"] -version = "0.0.8" +version = "0.0.9" registry = "https://github.com/weenachuangkud/FastCast2" realm = "shared" # Or "server" or "client" depending on your package's scope