From a87ad55f9bd6eb7af264604093b5565b56c82675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ademir=20S=C3=A1nchez?= Date: Sun, 21 May 2023 11:10:33 -0600 Subject: [PATCH 1/5] feat(process): remove old processes + add new ones --- src/lib/process/process.spec.ts | 38 --------- src/lib/process/process.ts | 41 --------- src/lib/process/scheduler.spec.ts | 77 ----------------- src/lib/process/scheduler.ts | 112 ------------------------- src/lib/processes/process.spec.ts | 3 + src/lib/processes/process/index.ts | 36 ++++++++ src/lib/processes/process/types.d.ts | 27 ++++++ src/lib/processes/scheduler/index.ts | 88 +++++++++++++++++++ src/lib/processes/scheduler/types.d.ts | 1 + 9 files changed, 155 insertions(+), 268 deletions(-) delete mode 100644 src/lib/process/process.spec.ts delete mode 100644 src/lib/process/process.ts delete mode 100644 src/lib/process/scheduler.spec.ts delete mode 100644 src/lib/process/scheduler.ts create mode 100644 src/lib/processes/process.spec.ts create mode 100644 src/lib/processes/process/index.ts create mode 100644 src/lib/processes/process/types.d.ts create mode 100644 src/lib/processes/scheduler/index.ts create mode 100644 src/lib/processes/scheduler/types.d.ts diff --git a/src/lib/process/process.spec.ts b/src/lib/process/process.spec.ts deleted file mode 100644 index ca2b5be..0000000 --- a/src/lib/process/process.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -/// - -import Tina from "../.."; -import { Process } from "./process"; -import Scheduler from "./scheduler"; - -export = () => { - describe("Process", () => { - it("should create new process", () => { - const process = new Process("myName", Scheduler); - expect(process).to.be.ok(); - expect(process.name).to.equal("myName"); - }); - - it("should return the same Process when using Tina.process() and new Process()", () => { - const process1 = new Process("shouldMatchProcess", Scheduler); - const process2 = Tina.process("shouldMatchProcess"); - expect(process1).to.equal(process2); - }); - - it("should add to ticker on .resume()", () => { - const process = new Process("callMeAnything", Scheduler); - expect(Scheduler.hasProcess(process.name)).to.equal(false); - process.resume(); - expect(Scheduler.hasProcess(process.name)).to.equal(true); - Scheduler.removeProcess(process); - }); - - it("should suspend on .suspend()", () => { - const process = new Process("", Scheduler); - expect(process.isSuspended).to.equal(false); - - process.suspend(23); - expect(process.isSuspended).to.equal(true); - expect(process.suspensionTime).to.equal(23); - }); - }); -}; diff --git a/src/lib/process/process.ts b/src/lib/process/process.ts deleted file mode 100644 index 5e6bc4f..0000000 --- a/src/lib/process/process.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { EventEmitter } from "../events"; -import Scheduler from "./scheduler"; - -type ProcessScheduler = typeof Scheduler; -interface Events { - _default: [dt: number]; -} - -export class Process extends EventEmitter { - public static processes = new Map(); - - public name: string; - public isSuspended = false; - public suspensionTime = -1; - - private ticker: ProcessScheduler; - - constructor(name: string, ticker: ProcessScheduler) { - super(); - - this.name = name; - this.ticker = ticker; - - // Add to static list of all created Processes - Process.processes.set(name, this); - } - - public resume(): void { - this.isSuspended = false; - this.ticker.addProcess(this); - } - - public suspend(ticks = 1): void { - this.suspensionTime = ticks; - this.isSuspended = true; - } - - public tick(dt: number): void { - return this.emit("_default", dt); - } -} diff --git a/src/lib/process/scheduler.spec.ts b/src/lib/process/scheduler.spec.ts deleted file mode 100644 index de32713..0000000 --- a/src/lib/process/scheduler.spec.ts +++ /dev/null @@ -1,77 +0,0 @@ -/// - -import { Process } from "./process"; -import Scheduler, { ProcessScheduler } from "./scheduler"; - -export = (): void => { - describe("Scheduler", () => { - it("should exist", () => { - expect(Scheduler).to.be.ok(); - }); - - it("should be able to add and remove processes from it", () => { - const ticker = new ProcessScheduler(); - const process = new Process("addAndRemove", ticker); - expect(ticker.hasProcess(process.name)).to.equal(false); - ticker.addProcess(process); - expect(ticker.hasProcess(process.name)).to.equal(true); - ticker.removeProcess(process); - expect(ticker.hasProcess(process.name)).to.equal(false); - }); - - it("should call unsuspended processes", () => { - const ticker = new ProcessScheduler(); - ticker.start(); - const process = new Process("unsuspended", ticker); - ticker.addProcess(process); - - let called = false; - process.when().do(() => (called = true)); - task.wait(0.5); - - ticker.destroy(); - expect(called).to.equal(true); - }); - - it("should not call suspended processes", () => { - const ticker = new ProcessScheduler(); - const process = new Process("suspended", ticker); - ticker.start(); - ticker.addProcess(process); - process.suspend(1000); // 20 ticks = 1 second at 20 TPS - - let called = false; - process.when().do(() => (called = true)); - task.wait(0.5); - - ticker.destroy(); - - // Should timeout - expect(called).to.equal(false); - }); - - it("should be able to remove process during a tick", () => { - const ticker = new ProcessScheduler(); - const process1 = new Process("1", ticker); - const process2 = new Process("2", ticker); - ticker.start(); - - let called = false; - process1.when().do(() => ticker.removeProcess(process1)); - process2.when().do(() => { - called = true; - }); - - process1.resume(); - process2.resume(); - - task.wait(0.5); - - const hasProcess = ticker.hasProcess(process1.name); - ticker.destroy(); - - expect(hasProcess).to.equal(false); - expect(called).to.equal(true); - }); - }); -}; diff --git a/src/lib/process/scheduler.ts b/src/lib/process/scheduler.ts deleted file mode 100644 index f12b554..0000000 --- a/src/lib/process/scheduler.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { Process } from "./process"; - -const RunService = game.GetService("RunService"); - -/** - * - */ -export class ProcessScheduler { - private static TPS = 20; // Grab value from tina.yaml when able - private lastTick: number; - private timeBetweenTicks: number; - private connection?: RBXScriptConnection; - private processes: Array = new Array(); - private processesToRemove: Array = new Array(); - - /** Used to keep track of when the .update() method is running since .removeProcess() could be called during a Process callback. */ - private updating = false; - - constructor() { - this.timeBetweenTicks = 1 / ProcessScheduler.TPS; - this.lastTick = os.clock(); - } - - private onHeartbeat(): void { - const currentTick = os.clock(); - const deltaTime = currentTick - this.lastTick; - if (deltaTime >= this.timeBetweenTicks) { - // Adjust lastTick to based on timeBetweenTicks to keep interval relatively stable - this.lastTick = currentTick - (deltaTime % this.timeBetweenTicks); - this.update(deltaTime); - } - } - - private update(dt: number): void { - // Update has started - this.updating = true; - - for (const process of this.processes) { - // Update suspended processes - if (process.isSuspended) { - process.suspensionTime -= 1; - if (process.suspensionTime < 0) { - process.isSuspended = false; - } - } - - // Run active processes - if (process.isSuspended) continue; - try { - process.tick(dt); - } catch (error) { - // TODO: log any errors properly. - } - } - - // Remove processes in queue - for (const process of this.processesToRemove) { - this._removeProcess(process); - } - this.processesToRemove = new Array(); - - // Update has finished - this.updating = false; - } - - public addProcess(process: Process): void { - if (this.processes.includes(process)) return; - - this.processes.push(process); - } - - public removeProcess(process: Process): void { - // If Processes are currently being updated then deffer removal until updating has finished - if (this.updating) { - this.processesToRemove.push(process); - } else { - this._removeProcess(process); - } - } - - private _removeProcess(process: Process): void { - const index = this.processes.indexOf(process); - if (index < 0) return; - this.processes.remove(index); - } - - public hasProcess(name: string): boolean { - return !!this.processes.find(p => p.name === name); - } - - public getProcess(name: string): Process | undefined { - return this.processes.find(p => p.name === name); - } - - public start(): void { - if (this.connection) return; // TODO: log that they're doing something stupid. - this.connection = RunService.Heartbeat.Connect(() => this.onHeartbeat()); - } - - /** - * @hidden - * Remove all references and disconnect all connections - */ - public destroy(): void { - this.connection?.Disconnect(); - this.processes = new Array(); - this.processesToRemove = new Array(); - } -} - -const Scheduler = new ProcessScheduler(); -export default Scheduler; diff --git a/src/lib/processes/process.spec.ts b/src/lib/processes/process.spec.ts new file mode 100644 index 0000000..2c712da --- /dev/null +++ b/src/lib/processes/process.spec.ts @@ -0,0 +1,3 @@ +/// + +export = (): void => {}; diff --git a/src/lib/processes/process/index.ts b/src/lib/processes/process/index.ts new file mode 100644 index 0000000..d33e4fc --- /dev/null +++ b/src/lib/processes/process/index.ts @@ -0,0 +1,36 @@ +import { EventEmitter } from "../../events"; +import { Scheduler } from "../scheduler"; +import { TProcessStatus } from "../scheduler/types"; +import { IProcessImplementation } from "./types"; + +interface IProcessEvent { + _default: [dt: number]; +} + +export class Process extends EventEmitter implements IProcessImplementation { + public lastTick: number = os.clock(); + + public ticksPerSecond?: number; + + constructor(public readonly name: string, public readonly executionGroup?: RBXScriptSignal) { + super(); + + Scheduler.add(name, this); + } + + public resume(): void { + return Scheduler.unsuspend(this.name); + } + + public suspend(ticks: number): void { + return Scheduler.suspend(this.name, ticks); + } + + public status(): TProcessStatus { + return Scheduler.status(this.name); + } + + public _update(dt: number): void { + return this.emit("_default", dt); + } +} diff --git a/src/lib/processes/process/types.d.ts b/src/lib/processes/process/types.d.ts new file mode 100644 index 0000000..3021b67 --- /dev/null +++ b/src/lib/processes/process/types.d.ts @@ -0,0 +1,27 @@ +import { TProcessStatus } from "../scheduler/types"; + +export interface IProcessImplementation { + /** + * Initializes current process. + */ + resume(): void; + + /** + * Suspends the current process by the given ticks, defaults to 1. + * + * @param ticks the amount of ticks to suspend the process. + */ + suspend(ticks: number): void; + + /** + * Returns the current status of the process. + * + * @returns A string for it's status. + */ + status(): TProcessStatus; + + /** + * @hidden (internal usage, DO NOT USE) + */ + _update(dt: number): void; +} diff --git a/src/lib/processes/scheduler/index.ts b/src/lib/processes/scheduler/index.ts new file mode 100644 index 0000000..589079a --- /dev/null +++ b/src/lib/processes/scheduler/index.ts @@ -0,0 +1,88 @@ +import { RunService } from "@rbxts/services"; + +import { Process } from "../process"; +import { TProcessStatus } from "./types"; + +export namespace Scheduler { + const processes: Map = new Map(); + const suspended: Map = new Map(); + + const connections: Map = new Map(); + + /** + * Updates current process. + * + * @param name process's name + * @param process the process to update + * @param dt the delta time provided within frame to frame. + */ + const update = (name: string, process: Process, dt: number): void => { + const ticksPerSecond = process.ticksPerSecond; + + if (ticksPerSecond !== undefined) { + const lastTick = process.lastTick; + const currentTick = os.clock(); + + // eslint-disable-next-line no-param-reassign + dt = currentTick - lastTick; + + if (dt < ticksPerSecond) return; + + process.lastTick = currentTick - (dt % ticksPerSecond); + } + + if (suspended.has(name)) { + const leftTicks = suspended.get(name)! - 1; + + if (leftTicks <= 0) { + suspended.delete(name); + } else { + return void suspended.set(name, leftTicks); + } + } + + return process._update(dt); + }; + + export function add(name: string, process: Process): void { + return void task.defer(() => { + if (process.executionGroup) { + const connection = process.executionGroup.Connect((dt: number) => + update(name, process, dt), + ); + + return connections.set(name, connection); + } + + processes.set(name, process); + }); + } + + export function remove(name: string): void { + connections.get(name)?.Disconnect(); + connections.delete(name); // Doesn't matter if it exists or not, it's just setting it to nil + + return void task.defer(() => processes.delete(name)); + } + + export function suspend(name: string, ticks: number): void { + return void suspended.set(name, ticks); + } + + export function unsuspend(name: string): void { + return void suspended.delete(name); + } + + export function status(name: string): TProcessStatus { + const status = processes.has(name) || connections.has(name); + + return status ? "active" : !status ? "dead" : suspended.has(name) ? "suspended" : "unknown"; + } + + /** + * Heartbeat scheduled Process (default) + */ + RunService.Heartbeat.Connect((dt: number) => { + for (const [name, process] of processes) update(name, process, dt); + }); +} diff --git a/src/lib/processes/scheduler/types.d.ts b/src/lib/processes/scheduler/types.d.ts new file mode 100644 index 0000000..0ebafa6 --- /dev/null +++ b/src/lib/processes/scheduler/types.d.ts @@ -0,0 +1 @@ +export type TProcessStatus = "active" | "suspended" | "dead" | "unknown"; From 2e3c557d8fa32c6ef4f446d3896e5186a3375d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ademir=20S=C3=A1nchez?= Date: Sun, 21 May 2023 11:16:45 -0600 Subject: [PATCH 2/5] fix(process): build problem Forgot to modify the src file where the imports are done. --- src/index.ts | 21 +++++++++++++++------ src/lib/processes/scheduler/index.ts | 8 ++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index 6e82208..772955b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,8 +12,9 @@ import { Internals } from "./lib/net/internal"; import { Client } from "./lib/net/util/client"; import { Identifiers } from "./lib/net/util/identifiers"; import { Server } from "./lib/net/util/server"; -import { Process } from "./lib/process/process"; -import Scheduler from "./lib/process/scheduler"; +import { Process } from "./lib/processes/process"; +import { IProcessImplementation } from "./lib/processes/process/types"; +import { Scheduler } from "./lib/processes/scheduler"; import { Component, ComponentData, Flyweight, FlyweightData, TagComponent } from "./lib/types/ecs"; import { Users } from "./lib/user"; import { DefaultUserDeclaration } from "./lib/user/default/types"; @@ -92,14 +93,18 @@ namespace Tina { * Used to add new processes to the processor. * * @param name process name to add. + * @param executionGroup your own executionGroup to run the process on. * @returns a Process object. */ - export function process(name: string): Process { - if (Process.processes.has(name)) { - return Process.processes.get(name)!; + export function process( + name: string, + executionGroup?: RBXScriptSignal, + ): IProcessImplementation { + if (Scheduler.has(name)) { + return Scheduler.get(name)!; } - return new Process(name, Scheduler); + return new Process(name, executionGroup); } export const log: Scope = Logger.scope("TINA"); @@ -246,6 +251,10 @@ export { User, Users } from "./lib/user"; /** State namespace */ export { State } from "./lib/state"; +/** Process class and scheduler namespace */ +export { Process } from "./lib/processes/process"; +export { Scheduler } from "./lib/processes/scheduler"; + /** Container export */ export { Container } from "./lib/container"; diff --git a/src/lib/processes/scheduler/index.ts b/src/lib/processes/scheduler/index.ts index 589079a..4564c3f 100644 --- a/src/lib/processes/scheduler/index.ts +++ b/src/lib/processes/scheduler/index.ts @@ -65,6 +65,14 @@ export namespace Scheduler { return void task.defer(() => processes.delete(name)); } + export function get(name: string): Process | undefined { + return processes.get(name); + } + + export function has(name: string): boolean { + return processes.has(name); + } + export function suspend(name: string, ticks: number): void { return void suspended.set(name, ticks); } From f01ab8f9a8a7104a2a7a3dad33e1c168cf407116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ademir=20S=C3=A1nchez?= Date: Sun, 21 May 2023 17:44:53 -0600 Subject: [PATCH 3/5] fix & tests(process): fix & write tests --- src/index.ts | 21 -------------- src/lib/processes/process.spec.ts | 41 +++++++++++++++++++++++++++- src/lib/processes/process/index.ts | 18 +++++------- src/lib/processes/process/types.d.ts | 5 ---- src/lib/processes/scheduler/index.ts | 23 ++++++++++------ 5 files changed, 61 insertions(+), 47 deletions(-) diff --git a/src/index.ts b/src/index.ts index 772955b..f2a8f91 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,9 +12,6 @@ import { Internals } from "./lib/net/internal"; import { Client } from "./lib/net/util/client"; import { Identifiers } from "./lib/net/util/identifiers"; import { Server } from "./lib/net/util/server"; -import { Process } from "./lib/processes/process"; -import { IProcessImplementation } from "./lib/processes/process/types"; -import { Scheduler } from "./lib/processes/scheduler"; import { Component, ComponentData, Flyweight, FlyweightData, TagComponent } from "./lib/types/ecs"; import { Users } from "./lib/user"; import { DefaultUserDeclaration } from "./lib/user/default/types"; @@ -89,24 +86,6 @@ namespace Tina { return new TinaCore(); } - /** - * Used to add new processes to the processor. - * - * @param name process name to add. - * @param executionGroup your own executionGroup to run the process on. - * @returns a Process object. - */ - export function process( - name: string, - executionGroup?: RBXScriptSignal, - ): IProcessImplementation { - if (Scheduler.has(name)) { - return Scheduler.get(name)!; - } - - return new Process(name, executionGroup); - } - export const log: Scope = Logger.scope("TINA"); /** diff --git a/src/lib/processes/process.spec.ts b/src/lib/processes/process.spec.ts index 2c712da..f536f07 100644 --- a/src/lib/processes/process.spec.ts +++ b/src/lib/processes/process.spec.ts @@ -1,3 +1,42 @@ /// -export = (): void => {}; +import { RunService } from "@rbxts/services"; + +import { Process } from "./process"; +import { Scheduler } from "./scheduler"; + +export = (): void => { + describe("Processes", () => { + it("should extend from a process correctly", () => { + class TestingProcess extends Process { + public update(): void {} + } + + const process = new TestingProcess("TestingProcess"); + + expect(Scheduler.has("TestingProcess")).to.be.equal(false); + + process.delete(); + }); + + it("should suspend process correctly", () => { + class TestingProcess extends Process { + constructor(name: string) { + super(name); + + this.executionGroup = RunService.PostSimulation; // Another syntax + } + + public update(): void {} + } + + const process = new TestingProcess("SuspendedProcess"); + + process.suspend(10); // Suspend it for 10 ticks + + expect(process.status()).to.be.equal("suspended"); + + process.delete(); + }); + }); +}; diff --git a/src/lib/processes/process/index.ts b/src/lib/processes/process/index.ts index d33e4fc..a1c0313 100644 --- a/src/lib/processes/process/index.ts +++ b/src/lib/processes/process/index.ts @@ -1,20 +1,14 @@ -import { EventEmitter } from "../../events"; +import { ExecutionGroup } from "../../ecs/system"; import { Scheduler } from "../scheduler"; import { TProcessStatus } from "../scheduler/types"; import { IProcessImplementation } from "./types"; -interface IProcessEvent { - _default: [dt: number]; -} - -export class Process extends EventEmitter implements IProcessImplementation { +export abstract class Process implements IProcessImplementation { public lastTick: number = os.clock(); public ticksPerSecond?: number; - constructor(public readonly name: string, public readonly executionGroup?: RBXScriptSignal) { - super(); - + constructor(public readonly name: string, public executionGroup?: ExecutionGroup) { Scheduler.add(name, this); } @@ -30,7 +24,9 @@ export class Process extends EventEmitter implements IProcessImpl return Scheduler.status(this.name); } - public _update(dt: number): void { - return this.emit("_default", dt); + public delete(): void { + return Scheduler.remove(this.name); } + + public abstract update(dt: number): void; } diff --git a/src/lib/processes/process/types.d.ts b/src/lib/processes/process/types.d.ts index 3021b67..08abcb2 100644 --- a/src/lib/processes/process/types.d.ts +++ b/src/lib/processes/process/types.d.ts @@ -19,9 +19,4 @@ export interface IProcessImplementation { * @returns A string for it's status. */ status(): TProcessStatus; - - /** - * @hidden (internal usage, DO NOT USE) - */ - _update(dt: number): void; } diff --git a/src/lib/processes/scheduler/index.ts b/src/lib/processes/scheduler/index.ts index 4564c3f..9344dde 100644 --- a/src/lib/processes/scheduler/index.ts +++ b/src/lib/processes/scheduler/index.ts @@ -1,5 +1,6 @@ import { RunService } from "@rbxts/services"; +import { ConnectionLike, ConnectionUtil } from "../../util/connection-util"; import { Process } from "../process"; import { TProcessStatus } from "./types"; @@ -7,7 +8,7 @@ export namespace Scheduler { const processes: Map = new Map(); const suspended: Map = new Map(); - const connections: Map = new Map(); + const connections: Map = new Map(); /** * Updates current process. @@ -34,20 +35,20 @@ export namespace Scheduler { if (suspended.has(name)) { const leftTicks = suspended.get(name)! - 1; - if (leftTicks <= 0) { - suspended.delete(name); - } else { + if (leftTicks > 0) { return void suspended.set(name, leftTicks); } + + suspended.delete(name); } - return process._update(dt); + return process.update(dt); }; export function add(name: string, process: Process): void { return void task.defer(() => { if (process.executionGroup) { - const connection = process.executionGroup.Connect((dt: number) => + const connection = ConnectionUtil.connect(process.executionGroup, (dt: number) => update(name, process, dt), ); @@ -59,8 +60,10 @@ export namespace Scheduler { } export function remove(name: string): void { - connections.get(name)?.Disconnect(); - connections.delete(name); // Doesn't matter if it exists or not, it's just setting it to nil + if (connections.has(name)) { + ConnectionUtil.disconnect(connections.get(name)!); + connections.delete(name); + } return void task.defer(() => processes.delete(name)); } @@ -84,7 +87,9 @@ export namespace Scheduler { export function status(name: string): TProcessStatus { const status = processes.has(name) || connections.has(name); - return status ? "active" : !status ? "dead" : suspended.has(name) ? "suspended" : "unknown"; + print(processes.has(name), connections.has(name)); + + return suspended.has(name) ? "suspended" : status ? "active" : !status ? "dead" : "unknown"; } /** From 780373033b46e76ef607d657216802f78144557c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ademir=20S=C3=A1nchez?= Date: Sun, 21 May 2023 23:38:49 -0600 Subject: [PATCH 4/5] feat(process): rework process again --- src/lib/processes/process.spec.ts | 41 ------ src/lib/processes/process/index.ts | 53 ++++--- src/lib/processes/process/types.d.ts | 23 +--- src/lib/processes/scheduler/index.ts | 182 +++++++++++++++---------- src/lib/processes/scheduler/types.d.ts | 1 - 5 files changed, 150 insertions(+), 150 deletions(-) diff --git a/src/lib/processes/process.spec.ts b/src/lib/processes/process.spec.ts index f536f07..6eba91e 100644 --- a/src/lib/processes/process.spec.ts +++ b/src/lib/processes/process.spec.ts @@ -1,42 +1 @@ /// - -import { RunService } from "@rbxts/services"; - -import { Process } from "./process"; -import { Scheduler } from "./scheduler"; - -export = (): void => { - describe("Processes", () => { - it("should extend from a process correctly", () => { - class TestingProcess extends Process { - public update(): void {} - } - - const process = new TestingProcess("TestingProcess"); - - expect(Scheduler.has("TestingProcess")).to.be.equal(false); - - process.delete(); - }); - - it("should suspend process correctly", () => { - class TestingProcess extends Process { - constructor(name: string) { - super(name); - - this.executionGroup = RunService.PostSimulation; // Another syntax - } - - public update(): void {} - } - - const process = new TestingProcess("SuspendedProcess"); - - process.suspend(10); // Suspend it for 10 ticks - - expect(process.status()).to.be.equal("suspended"); - - process.delete(); - }); - }); -}; diff --git a/src/lib/processes/process/index.ts b/src/lib/processes/process/index.ts index a1c0313..6fefa2a 100644 --- a/src/lib/processes/process/index.ts +++ b/src/lib/processes/process/index.ts @@ -1,32 +1,53 @@ -import { ExecutionGroup } from "../../ecs/system"; -import { Scheduler } from "../scheduler"; -import { TProcessStatus } from "../scheduler/types"; -import { IProcessImplementation } from "./types"; +import { Scheduler, TinaScheduler } from "../scheduler"; +import { ProcessStatus } from "./types"; -export abstract class Process implements IProcessImplementation { - public lastTick: number = os.clock(); +export abstract class Process { + public scheduler: Scheduler; - public ticksPerSecond?: number; + constructor(scheduler?: Scheduler) { + this.scheduler = scheduler ?? TinaScheduler; + this.scheduler.schedule(this); + } - constructor(public readonly name: string, public executionGroup?: ExecutionGroup) { - Scheduler.add(name, this); + /** + * Suspends current process by the given ticks (defaults to 1). + * + * @param ticks amount of ticks. + */ + public suspend(ticks = 1): void { + return this.scheduler.suspend(this, ticks); } + /** + * Resumes current process. + */ public resume(): void { - return Scheduler.unsuspend(this.name); + return this.scheduler.unsuspend(this); } - public suspend(ticks: number): void { - return Scheduler.suspend(this.name, ticks); - } + /** + * Returns current process Status. + * + * @returns a string indicating it's current status. + */ + public status(): ProcessStatus { + const isActive = this.scheduler.has(this); + const isSuspended = this.scheduler.isSuspended(this); - public status(): TProcessStatus { - return Scheduler.status(this.name); + return isSuspended ? "suspended" : isActive ? "active" : !isActive ? "dead" : "unknown"; } + /** + * Deletes current process, not anymore needed. + */ public delete(): void { - return Scheduler.remove(this.name); + return this.scheduler.unschedule(this); } + /** + * Method invoked every tick (specified on the scheduler) + * + * @param dt delta time between ticks. + */ public abstract update(dt: number): void; } diff --git a/src/lib/processes/process/types.d.ts b/src/lib/processes/process/types.d.ts index 08abcb2..6cf2eb9 100644 --- a/src/lib/processes/process/types.d.ts +++ b/src/lib/processes/process/types.d.ts @@ -1,22 +1 @@ -import { TProcessStatus } from "../scheduler/types"; - -export interface IProcessImplementation { - /** - * Initializes current process. - */ - resume(): void; - - /** - * Suspends the current process by the given ticks, defaults to 1. - * - * @param ticks the amount of ticks to suspend the process. - */ - suspend(ticks: number): void; - - /** - * Returns the current status of the process. - * - * @returns A string for it's status. - */ - status(): TProcessStatus; -} +export type ProcessStatus = "active" | "dead" | "suspended" | "unknown"; diff --git a/src/lib/processes/scheduler/index.ts b/src/lib/processes/scheduler/index.ts index 9344dde..9fc9fe5 100644 --- a/src/lib/processes/scheduler/index.ts +++ b/src/lib/processes/scheduler/index.ts @@ -1,101 +1,143 @@ import { RunService } from "@rbxts/services"; -import { ConnectionLike, ConnectionUtil } from "../../util/connection-util"; +import { ExecutionGroup } from "../../ecs/system"; +import { ConnectionUtil } from "../../util/connection-util"; import { Process } from "../process"; -import { TProcessStatus } from "./types"; -export namespace Scheduler { - const processes: Map = new Map(); - const suspended: Map = new Map(); +export abstract class Scheduler { + public executionGroup?: ExecutionGroup; + public ticksPerSecond?: number; - const connections: Map = new Map(); + private processes: Map = new Map(); + private suspended: Map = new Map(); + + private stopped = false; + + constructor(configuration?: { executionGroup?: ExecutionGroup; ticksPerSecond?: number }) { + this.executionGroup = configuration?.executionGroup ?? RunService.Heartbeat; + + ConnectionUtil.connect(this.executionGroup, (dt: number) => { + this.update(dt); + }); + } /** - * Updates current process. + * Schedules a new process. * - * @param name process's name - * @param process the process to update - * @param dt the delta time provided within frame to frame. + * @param process process to schedule. */ - const update = (name: string, process: Process, dt: number): void => { - const ticksPerSecond = process.ticksPerSecond; + public schedule(process: Process): void { + return void this.processes.set(process, os.clock()); + } - if (ticksPerSecond !== undefined) { - const lastTick = process.lastTick; - const currentTick = os.clock(); + /** + * Unschedules a current on-going process, deferred to the end of the current resumption cycle. + * + * @param process process to unschedule. + */ + public unschedule(process: Process): void { + return void task.defer(() => this.processes.delete(process)); + } - // eslint-disable-next-line no-param-reassign - dt = currentTick - lastTick; + /** + * Checks if a Scheduler has any matching process. + * + * @param process a process to check on. + */ + public has(process: Process): boolean { + return this.processes.has(process); + } - if (dt < ticksPerSecond) return; + /** + * Suspends specified process. + * + * @param process process to suspend. + * @param ticks ticks to suspend the process. + */ + public suspend(process: Process, ticks: number): void { + return void this.suspended.set(process, ticks); + } - process.lastTick = currentTick - (dt % ticksPerSecond); - } + /** + * Unsuspends specified process (to the end of the current resumption cycle). + * + * @param process process to unsuspend. + */ + public unsuspend(process: Process): void { + return void task.defer(() => this.suspended.delete(process)); + } - if (suspended.has(name)) { - const leftTicks = suspended.get(name)! - 1; + /** + * Checks if a process is currently suspended. + * + * @param process process to check on. + */ + public isSuspended(process: Process): boolean { + return this.suspended.has(process); + } - if (leftTicks > 0) { - return void suspended.set(name, leftTicks); - } + /** + * Stops current processes, can be resumed later on. + */ + public stop(): void { + this.stopped = true; + } - suspended.delete(name); - } + /** + * Resumes already going processes. + */ + public resume(): void { + this.stopped = false; + } - return process.update(dt); - }; + /** + * Clears all current processes going on. + */ + public clear(): void { + return void task.defer(() => this.processes.clear()); + } - export function add(name: string, process: Process): void { - return void task.defer(() => { - if (process.executionGroup) { - const connection = ConnectionUtil.connect(process.executionGroup, (dt: number) => - update(name, process, dt), - ); + /** + * Updates all available processes. + * + * @param dt delta time between frames (or ticks) + */ + private update(dt: number): void { + if (this.stopped) return; - return connections.set(name, connection); - } + for (const [process, lastTick] of this.processes) { + let delta = dt; - processes.set(name, process); - }); - } + if (this.ticksPerSecond !== undefined) { + const currentTick = os.clock(); - export function remove(name: string): void { - if (connections.has(name)) { - ConnectionUtil.disconnect(connections.get(name)!); - connections.delete(name); - } + delta = (currentTick - lastTick) * dt; + if (delta < this.ticksPerSecond) return; - return void task.defer(() => processes.delete(name)); - } + this.processes.set(process, currentTick - (delta % this.ticksPerSecond)); + } - export function get(name: string): Process | undefined { - return processes.get(name); - } + if (this.suspended.has(process)) { + const leftTicks = this.suspended.get(process)! - 1; - export function has(name: string): boolean { - return processes.has(name); - } + if (leftTicks > 0) { + return void this.suspended.set(process, leftTicks); + } - export function suspend(name: string, ticks: number): void { - return void suspended.set(name, ticks); - } + this.suspended.delete(process); + } - export function unsuspend(name: string): void { - return void suspended.delete(name); + return process.update(delta); + } } +} - export function status(name: string): TProcessStatus { - const status = processes.has(name) || connections.has(name); - - print(processes.has(name), connections.has(name)); +class InternalScheduler extends Scheduler { + constructor() { + super(); - return suspended.has(name) ? "suspended" : status ? "active" : !status ? "dead" : "unknown"; + this.executionGroup = RunService.Heartbeat; } - - /** - * Heartbeat scheduled Process (default) - */ - RunService.Heartbeat.Connect((dt: number) => { - for (const [name, process] of processes) update(name, process, dt); - }); } + +export const TinaScheduler = new InternalScheduler(); diff --git a/src/lib/processes/scheduler/types.d.ts b/src/lib/processes/scheduler/types.d.ts index 0ebafa6..e69de29 100644 --- a/src/lib/processes/scheduler/types.d.ts +++ b/src/lib/processes/scheduler/types.d.ts @@ -1 +0,0 @@ -export type TProcessStatus = "active" | "suspended" | "dead" | "unknown"; From eb44a23abbbe3bca47a703276a0bdeec7b6cf5d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ademir=20S=C3=A1nchez?= Date: Mon, 22 May 2023 20:26:29 -0600 Subject: [PATCH 5/5] fix(process): scheduler set --- src/lib/processes/process/index.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/processes/process/index.ts b/src/lib/processes/process/index.ts index 6fefa2a..edf9276 100644 --- a/src/lib/processes/process/index.ts +++ b/src/lib/processes/process/index.ts @@ -2,10 +2,9 @@ import { Scheduler, TinaScheduler } from "../scheduler"; import { ProcessStatus } from "./types"; export abstract class Process { - public scheduler: Scheduler; + public scheduler: Scheduler = TinaScheduler; - constructor(scheduler?: Scheduler) { - this.scheduler = scheduler ?? TinaScheduler; + constructor() { this.scheduler.schedule(this); }