diff --git a/packages/3d/build.config.ts b/packages/3d/build.config.ts
index 4ff93f3..7150ae2 100644
--- a/packages/3d/build.config.ts
+++ b/packages/3d/build.config.ts
@@ -3,6 +3,7 @@ import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
entries: [
'src/index',
+ 'src/pipeline/PhysicWorkerRun',
],
declaration: true,
clean: true,
diff --git a/packages/3d/src/core/FScene.ts b/packages/3d/src/core/FScene.ts
index d910a79..3651ffc 100644
--- a/packages/3d/src/core/FScene.ts
+++ b/packages/3d/src/core/FScene.ts
@@ -5,6 +5,8 @@ import type RAPIER from '@dimforge/rapier3d'
import type { FCamera } from '../cameras/FCamera'
import { FFixedCamera } from '../cameras/FFixedCamera'
import type { FLight } from '../lights/FLight'
+import { RenderPipeline } from '../pipeline/RenderPipeline'
+import { PhysicPipeline } from '../pipeline/PhysicPipeline'
import type { FComponent } from './FComponent'
import type { FRigidBody } from './FRigidBody'
import type { FCollider } from './FCollider'
@@ -115,17 +117,8 @@ export class FScene extends FSceneCore {
// Add renderer to DOM
this.__DOM_NODE__.appendChild(this.renderer.domElement)
- // Each frame
- this.onFrame((delta) => {
- // Call frame for each component
- this.components.forEach(component => component.frame(delta))
-
- // Call frame for the camera
- this.camera.frame(delta)
-
- // Render the scene
- this.renderer.render(this.scene, this.camera.__CAMERA__)
- })
+ // Initialize the render pipeline
+ this.addStandardPipeline(new RenderPipeline({ scene: this }))
// Call the onReady callbacks
this.__CALLBACKS_ON_READY__.forEach((callback) => {
@@ -143,21 +136,8 @@ export class FScene extends FSceneCore {
// Initialize Rapier event queue
this.eventQueue = new RAPIER.EventQueue(true)
- // onFrame loop
- this.onFrame((delta) => {
- // Step the physics world
- this.world.timestep = delta
- this.world.step(this.eventQueue)
-
- // Call frame for each collider and rigidBody
- this.colliders.forEach(collider => collider.frame(delta))
- this.rigidBodies.forEach(rigidBody => rigidBody.frame(delta))
-
- // Drain collision events
- this.eventQueue.drainCollisionEvents((handle1: RAPIER.ColliderHandle, handle2: RAPIER.ColliderHandle, started: boolean) => {
- this.handleCollision(handle1, handle2, started)
- })
- })
+ // Initialize the physic pipeline
+ this.addStandardPipeline(new PhysicPipeline({ scene: this }))
}
/**
diff --git a/packages/3d/src/pipeline/PhysicPipeline.ts b/packages/3d/src/pipeline/PhysicPipeline.ts
new file mode 100644
index 0000000..b2989f4
--- /dev/null
+++ b/packages/3d/src/pipeline/PhysicPipeline.ts
@@ -0,0 +1,35 @@
+import { StandardPipeline } from '@fibbojs/core'
+import type RAPIER from '@dimforge/rapier3d'
+import type { FScene } from '../core/FScene'
+
+export interface PhysicPipelineOptions {
+ scene: FScene
+}
+
+/**
+ * Render pipeline.
+ */
+export class PhysicPipeline extends StandardPipeline {
+ scene: FScene
+
+ constructor(options: PhysicPipelineOptions) {
+ super()
+ this.scene = options.scene
+ this.frameRate = 60
+ }
+
+ frame(delta: number) {
+ // Step the physics world
+ this.scene.world.timestep = delta
+ this.scene.world.step(this.scene.eventQueue)
+
+ // Call frame for each collider and rigidBody
+ this.scene.colliders.forEach(collider => collider.frame(delta))
+ this.scene.rigidBodies.forEach(rigidBody => rigidBody.frame(delta))
+
+ // Drain collision events
+ this.scene.eventQueue.drainCollisionEvents((handle1: RAPIER.ColliderHandle, handle2: RAPIER.ColliderHandle, started: boolean) => {
+ this.scene.handleCollision(handle1, handle2, started)
+ })
+ }
+}
diff --git a/packages/3d/src/pipeline/PhysicPipelineTest.ts b/packages/3d/src/pipeline/PhysicPipelineTest.ts
new file mode 100644
index 0000000..a983458
--- /dev/null
+++ b/packages/3d/src/pipeline/PhysicPipelineTest.ts
@@ -0,0 +1,20 @@
+import { BackgroundPipeline } from '@fibbojs/core'
+
+export enum PhysicPipelineCommands {
+ START = 'start',
+ STOP = 'stop',
+}
+
+/**
+ * Physic pipeline.
+ * @category Pipeline
+ */
+export class PhysicPipelineTest extends BackgroundPipeline {
+ constructor() {
+ super('../../3d/dist/pipeline/PhysicWorkerRun.mjs')
+ }
+
+ frame(_delta: number) {
+ // console.log('PhysicPipeline frame')
+ }
+}
diff --git a/packages/3d/src/pipeline/PhysicWorker.ts b/packages/3d/src/pipeline/PhysicWorker.ts
new file mode 100644
index 0000000..2b84f94
--- /dev/null
+++ b/packages/3d/src/pipeline/PhysicWorker.ts
@@ -0,0 +1,30 @@
+import { BackgroundWorker } from '@fibbojs/core'
+
+/**
+ * Physic web worker.
+ * @category Pipeline
+ */
+export class PhysicWorker extends BackgroundWorker {
+ constructor(sw: DedicatedWorkerGlobalScope) {
+ super(sw)
+ }
+
+ frame(delta: number) {
+ console.log(`Worker : ${delta}`)
+
+ /*
+ // Step the physics world
+ this.world.timestep = delta
+ this.world.step(this.eventQueue)
+
+ // Call frame for each collider and rigidBody
+ this.colliders.forEach(collider => collider.frame(delta))
+ this.rigidBodies.forEach(rigidBody => rigidBody.frame(delta))
+
+ // Drain collision events
+ this.eventQueue.drainCollisionEvents((handle1: RAPIER.ColliderHandle, handle2: RAPIER.ColliderHandle, started: boolean) => {
+ this.handleCollision(handle1, handle2, started)
+ })
+ */
+ }
+}
diff --git a/packages/core/src/pipeline/RenderPipelineWorker.ts b/packages/3d/src/pipeline/PhysicWorkerRun.ts
similarity index 56%
rename from packages/core/src/pipeline/RenderPipelineWorker.ts
rename to packages/3d/src/pipeline/PhysicWorkerRun.ts
index 71a5619..3117dc1 100644
--- a/packages/core/src/pipeline/RenderPipelineWorker.ts
+++ b/packages/3d/src/pipeline/PhysicWorkerRun.ts
@@ -1,8 +1,8 @@
///
-import { RenderPipeline } from './RenderPipeline'
+import { PhysicWorker } from './PhysicWorker'
export type {}
declare let self: DedicatedWorkerGlobalScope
-new RenderPipeline(self)
+new PhysicWorker(self)
diff --git a/packages/3d/src/pipeline/RenderPipeline.ts b/packages/3d/src/pipeline/RenderPipeline.ts
new file mode 100644
index 0000000..a4f56d9
--- /dev/null
+++ b/packages/3d/src/pipeline/RenderPipeline.ts
@@ -0,0 +1,30 @@
+import { StandardPipeline } from '@fibbojs/core'
+import type { FScene } from '../core/FScene'
+
+export interface RenderPipelineOptions {
+ scene: FScene
+}
+
+/**
+ * Render pipeline.
+ */
+export class RenderPipeline extends StandardPipeline {
+ scene: FScene
+
+ constructor(options: RenderPipelineOptions) {
+ super()
+ this.scene = options.scene
+ this.frameRate = 60
+ }
+
+ frame(delta: number) {
+ // Call frame for each component
+ this.scene.components.forEach(component => component.frame(delta))
+
+ // Call frame for the camera
+ this.scene.camera.frame(delta)
+
+ // Render the scene
+ this.scene.renderer.render(this.scene.scene, this.scene.camera.__CAMERA__)
+ }
+}
diff --git a/packages/core/build.config.ts b/packages/core/build.config.ts
index b39054c..7acbef8 100644
--- a/packages/core/build.config.ts
+++ b/packages/core/build.config.ts
@@ -3,8 +3,6 @@ import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
entries: [
'src/index',
- 'src/pipeline/RenderPipelineWorker',
- 'src/pipeline/PhysicPipelineWorker',
],
declaration: true,
clean: true,
diff --git a/packages/core/src/FScene.ts b/packages/core/src/FScene.ts
index 184b66c..b05cf43 100644
--- a/packages/core/src/FScene.ts
+++ b/packages/core/src/FScene.ts
@@ -2,7 +2,10 @@ import type RAPIER2D from '@dimforge/rapier2d'
import type RAPIER3D from '@dimforge/rapier3d'
import type { FComponent } from './FComponent'
import type { FLight } from './FLight'
-import { CustomWorker } from './pipeline/CustomWorker'
+import type { BackgroundPipeline } from './pipeline/BackgroundPipeline'
+import type { StandardPipeline } from './pipeline/StandardPipeline'
+import { MainPipeline } from './pipeline/MainPipeline'
+import { PipelineState } from './pipeline/Pipeline'
export interface FSceneOptions {
gravity?: { x: number, y: number, z: number } | { x: number, y: number }
@@ -16,17 +19,15 @@ export interface FSceneOptions {
* @category Core
*/
export abstract class FScene {
- /**
- * Internal flags
- */
+ // Internal flags
public __IS_3D__: boolean = false
public __IS_2D__: boolean = false
/**
* Pipelines
*/
- private __RENDER_PIPELINE__: CustomWorker | null = null
- private __PHYSIC_PIPELINE__: CustomWorker | null = null
+ __STANDARD_PIPELINES__: StandardPipeline[]
+ __BACKGROUND_PIPELINES__: BackgroundPipeline[]
/**
* DOM element that the renderer will be appended to
@@ -97,11 +98,11 @@ export abstract class FScene {
this.__DOM_NODE__ = options.domNode
/**
- * Time management
+ * Pipelines
*/
- let lastTime = (new Date()).getTime()
- let currentTime = 0
- let delta = 0
+ this.__STANDARD_PIPELINES__ = []
+ this.__BACKGROUND_PIPELINES__ = []
+ this.addStandardPipeline(new MainPipeline({ scene: this }))
/**
* Auto loop function that calls the frame method every frame.
@@ -109,28 +110,32 @@ export abstract class FScene {
const autoLoop = () => {
requestAnimationFrame(autoLoop)
- // Calculate delta time
- currentTime = (new Date()).getTime()
- delta = (currentTime - lastTime) / 1000
- lastTime = currentTime
-
- // Call onFrame callbacks
- this.frame(delta)
+ this.__STANDARD_PIPELINES__.forEach((pipeline) => {
+ // If the pipeline should be running
+ if (pipeline.state === PipelineState.RUNNING) {
+ // Calculate elapsed time
+ const currentTime = (new Date()).getTime()
+ const elapsedTime = currentTime - pipeline.lastTime
+ // If enough time has passed to match the expected framerate
+ if (elapsedTime > 1000 / pipeline.frameRate) {
+ const delta = (currentTime - pipeline.lastTime) / 1000
+ // Keep track of the last time the pipeline was called
+ pipeline.lastTime = currentTime
+ // Call the pipeline
+ pipeline.frame(delta)
+ }
+ }
+ })
}
- if (options.autoLoop)
- autoLoop()
-
// Initialize the components array
this.components = []
// Initialize the lights array
this.lights = []
- // Initialize workers
- this.__RENDER_PIPELINE__ = new CustomWorker('./pipeline/RenderPipelineWorker.mjs')
- this.__RENDER_PIPELINE__.start()
- this.__PHYSIC_PIPELINE__ = new CustomWorker('./pipeline/PhysicPipelineWorker.mjs')
- this.__PHYSIC_PIPELINE__.start()
+ // Launch the autoLoop if needed
+ if (options.autoLoop)
+ autoLoop()
}
/**
@@ -171,6 +176,44 @@ export abstract class FScene {
this.__CALLBACKS_ON_LIGHT_REMOVED__.forEach(callback => callback(light))
}
+ /**
+ * Add a standard pipeline.
+ */
+ addStandardPipeline(pipeline: StandardPipeline) {
+ this.__STANDARD_PIPELINES__.push(pipeline)
+ pipeline.start()
+ }
+
+ /**
+ * Remove a standard pipeline.
+ */
+ removeStandardPipeline(pipeline: StandardPipeline) {
+ pipeline.stop()
+ const index = this.__STANDARD_PIPELINES__.indexOf(pipeline)
+ if (index !== -1) {
+ this.__STANDARD_PIPELINES__.splice(index, 1)
+ }
+ }
+
+ /**
+ * Add a background pipeline.
+ */
+ addBackgroundPipeline(pipeline: BackgroundPipeline) {
+ this.__BACKGROUND_PIPELINES__.push(pipeline)
+ pipeline.start()
+ }
+
+ /**
+ * Remove a background pipeline.
+ */
+ removeBackgroundPipeline(pipeline: BackgroundPipeline) {
+ pipeline.stop()
+ const index = this.__BACKGROUND_PIPELINES__.indexOf(pipeline)
+ if (index !== -1) {
+ this.__BACKGROUND_PIPELINES__.splice(index, 1)
+ }
+ }
+
/**
* Compute a frame with the given delta time.
* By default, it is called every frame, but this behavior can be changed by giving the `autoLoop` option as `false` when creating the scene.
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index fc13227..ba93098 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -9,10 +9,10 @@ export * from './FLight'
export * from './FScene'
// Pipeline
-export * from './pipeline/CustomWorker'
-export * from './pipeline/PhysicPipeline'
+export * from './pipeline/BackgroundPipeline'
+export * from './pipeline/BackgroundWorker'
export * from './pipeline/Pipeline'
-export * from './pipeline/RenderPipeline'
+export * from './pipeline/StandardPipeline'
// Types
export * from './types/FVector2'
diff --git a/packages/core/src/pipeline/BackgroundPipeline.ts b/packages/core/src/pipeline/BackgroundPipeline.ts
new file mode 100644
index 0000000..b2281a2
--- /dev/null
+++ b/packages/core/src/pipeline/BackgroundPipeline.ts
@@ -0,0 +1,33 @@
+import { Pipeline, PipelineCommands } from './Pipeline'
+
+/**
+ * A pipeline that abstract the usage of a web worker.
+ * It provides better type checking, and more control over the worker.
+ * @category Pipeline
+ */
+export abstract class BackgroundPipeline extends Pipeline {
+ worker: Worker
+
+ constructor(path: string) {
+ super()
+ /*
+ console.log(import.meta.url)
+ console.log(new URL(path, import.meta.url).href)
+ */
+ this.worker = new Worker(new URL(path, import.meta.url), { type: 'module' })
+ }
+
+ /**
+ * Start the corresponding pipeline.
+ */
+ start(): void {
+ this.worker.postMessage(PipelineCommands.START)
+ }
+
+ /**
+ * Stop the corresponding pipeline.
+ */
+ stop(): void {
+ this.worker.postMessage(PipelineCommands.STOP)
+ }
+}
diff --git a/packages/core/src/pipeline/BackgroundWorker.ts b/packages/core/src/pipeline/BackgroundWorker.ts
new file mode 100644
index 0000000..f447635
--- /dev/null
+++ b/packages/core/src/pipeline/BackgroundWorker.ts
@@ -0,0 +1,65 @@
+///
+
+import { Pipeline, PipelineCommands, PipelineState } from './Pipeline'
+
+/**
+ * Hided worker behind the BackgroundPipeline class.
+ * @category Pipeline
+ */
+export abstract class BackgroundWorker extends Pipeline {
+ /**
+ * The web worker instance.
+ */
+ sw: DedicatedWorkerGlobalScope
+ /**
+ * The interval id that is used to run the pipeline.
+ * Corresponds to the id returned by setInterval.
+ */
+ intervalId: ReturnType | null = null
+
+ constructor(sw: DedicatedWorkerGlobalScope) {
+ super()
+ // Save the web worker instance
+ this.sw = sw
+ this.sw.addEventListener('message', (event) => {
+ this.handleMessage(event)
+ })
+ }
+
+ /**
+ * Handle a message sent to the pipeline.
+ * @param event The message event.
+ */
+ handleMessage(event: MessageEvent) {
+ const command = event.data
+
+ if (command === PipelineCommands.START) {
+ this.start()
+ }
+ else if (command === PipelineCommands.STOP) {
+ this.stop()
+ }
+ }
+
+ start(): void {
+ if (this.intervalId !== null) {
+ return
+ }
+ // Start the pipeline by setting an interval with the frame rate
+ this.intervalId = setInterval(() => {
+ this.frame(1000 / this.frameRate / 1000)
+ }, 1000 / this.frameRate)
+ // Update the pipeline state
+ this.state = PipelineState.RUNNING
+ }
+
+ stop(): void {
+ // Stop the pipeline by clearing the interval
+ if (this.intervalId !== null) {
+ clearInterval(this.intervalId)
+ }
+ this.intervalId = null
+ // Update the pipeline state
+ this.state = PipelineState.STOPPED
+ }
+}
diff --git a/packages/core/src/pipeline/CustomWorker.ts b/packages/core/src/pipeline/CustomWorker.ts
deleted file mode 100644
index c6d2ec9..0000000
--- a/packages/core/src/pipeline/CustomWorker.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { PipelineCommands } from './Pipeline'
-
-/**
- * A custom worker that extends the Worker class.
- * It provides better type checking, and more control over the worker.
- * @category Pipeline
- */
-export class CustomWorker extends Worker {
- constructor(path: string) {
- super(new URL(path, import.meta.url), { type: 'module' })
- }
-
- /**
- * Start the corresponding pipeline.
- */
- start() {
- this.postMessage(PipelineCommands.START)
- }
-
- /**
- * Stop the corresponding pipeline.
- */
- stop() {
- this.postMessage(PipelineCommands.STOP)
- }
-}
diff --git a/packages/core/src/pipeline/MainPipeline.ts b/packages/core/src/pipeline/MainPipeline.ts
new file mode 100644
index 0000000..b92cf2a
--- /dev/null
+++ b/packages/core/src/pipeline/MainPipeline.ts
@@ -0,0 +1,23 @@
+import type { FScene } from '../FScene'
+import { StandardPipeline } from './StandardPipeline'
+
+export interface MainPipelineOptions {
+ scene: FScene
+}
+
+/**
+ * Main game loop.
+ */
+export class MainPipeline extends StandardPipeline {
+ scene: FScene
+
+ constructor(options: MainPipelineOptions) {
+ super()
+ this.scene = options.scene
+ this.frameRate = 60
+ }
+
+ frame(delta: number) {
+ this.scene.frame(delta)
+ }
+}
diff --git a/packages/core/src/pipeline/PhysicPipeline.ts b/packages/core/src/pipeline/PhysicPipeline.ts
deleted file mode 100644
index 5e9f2d2..0000000
--- a/packages/core/src/pipeline/PhysicPipeline.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Pipeline } from './Pipeline'
-
-/**
- * Pipeline to handle physic simulation tasks.
- * @category Pipeline
- */
-export class PhysicPipeline extends Pipeline {
- constructor(sw: DedicatedWorkerGlobalScope) {
- super(sw)
- }
-
- frame() {
- // console.log('PhysicPipeline frame')
- }
-}
diff --git a/packages/core/src/pipeline/PhysicPipelineWorker.ts b/packages/core/src/pipeline/PhysicPipelineWorker.ts
deleted file mode 100644
index 02d7108..0000000
--- a/packages/core/src/pipeline/PhysicPipelineWorker.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-///
-
-import { PhysicPipeline } from './PhysicPipeline'
-
-export type {}
-declare let self: DedicatedWorkerGlobalScope
-
-new PhysicPipeline(self)
diff --git a/packages/core/src/pipeline/Pipeline.ts b/packages/core/src/pipeline/Pipeline.ts
index ff5e10f..c42335d 100644
--- a/packages/core/src/pipeline/Pipeline.ts
+++ b/packages/core/src/pipeline/Pipeline.ts
@@ -11,24 +11,14 @@ export enum PipelineState {
}
/**
- * Pipeline class that abstract the usage of a web worker.
- * This is used for running background tasks that are generally CPU intensive.
+ * Pipeline class that helps handling many processes at a time.
* @category Pipeline
*/
export abstract class Pipeline {
- /**
- * The web worker instance.
- */
- sw: DedicatedWorkerGlobalScope
/**
* The current state of the pipeline.
*/
state: PipelineState
- /**
- * The interval id that is used to run the pipeline.
- * Corresponds to the id returned by setInterval.
- */
- intervalId: ReturnType | null = null
/**
* The frame rate of the pipeline.
* This is the number of frames per second that the pipeline will run at.
@@ -37,12 +27,7 @@ export abstract class Pipeline {
*/
frameRate: number
- constructor(sw: DedicatedWorkerGlobalScope) {
- // Save the web worker instance
- this.sw = sw
- this.sw.addEventListener('message', (event) => {
- this.handleMessage(event)
- })
+ constructor() {
// Set the initial state of the pipeline
this.state = PipelineState.STOPPED
this.frameRate = 30
@@ -52,34 +37,15 @@ export abstract class Pipeline {
* The frame method is the main method that is called by the pipeline.
* It should implement the desired behavior of the pipeline.
*/
- abstract frame(): void
+ abstract frame(delta: number): void
/**
- * Handle a message sent to the pipeline.
- * @param event The message event.
+ * Start the pipeline
*/
- handleMessage(event: MessageEvent) {
- const command = event.data
+ abstract start(): void
- if (command === PipelineCommands.START) {
- if (this.intervalId !== null) {
- return
- }
- // Start the pipeline by setting an interval with the frame rate
- this.intervalId = setInterval(() => {
- this.frame()
- }, 1000 / this.frameRate)
- // Update the pipeline state
- this.state = PipelineState.RUNNING
- }
- else if (command === PipelineCommands.STOP) {
- // Stop the pipeline by clearing the interval
- if (this.intervalId !== null) {
- clearInterval(this.intervalId)
- }
- this.intervalId = null
- // Update the pipeline state
- this.state = PipelineState.STOPPED
- }
- }
+ /**
+ * Stop the pipeline
+ */
+ abstract stop(): void
}
diff --git a/packages/core/src/pipeline/RenderPipeline.ts b/packages/core/src/pipeline/RenderPipeline.ts
deleted file mode 100644
index 0b4cba4..0000000
--- a/packages/core/src/pipeline/RenderPipeline.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Pipeline } from './Pipeline'
-
-/**
- * Pipeline to handle rendering tasks.
- * @category Pipeline
- */
-export class RenderPipeline extends Pipeline {
- constructor(sw: DedicatedWorkerGlobalScope) {
- super(sw)
- }
-
- frame() {
- // console.log('RenderPipeline frame')
- }
-}
diff --git a/packages/core/src/pipeline/StandardPipeline.ts b/packages/core/src/pipeline/StandardPipeline.ts
new file mode 100644
index 0000000..2cbee98
--- /dev/null
+++ b/packages/core/src/pipeline/StandardPipeline.ts
@@ -0,0 +1,24 @@
+import { Pipeline, PipelineState } from './Pipeline'
+
+/**
+ * Pipeline class.
+ * @category Pipeline
+ */
+export abstract class StandardPipeline extends Pipeline {
+ lastTime: number
+
+ constructor() {
+ super()
+ this.lastTime = (new Date()).getTime()
+ }
+
+ start(): void {
+ // Update the pipeline state
+ this.state = PipelineState.RUNNING
+ }
+
+ stop(): void {
+ // Update the pipeline state
+ this.state = PipelineState.STOPPED
+ }
+}