diff --git a/source/Component/CommandBar/CommandBar.tsx b/source/Component/CommandBar/CommandBar.tsx index 4ae4121..2bb2f22 100644 --- a/source/Component/CommandBar/CommandBar.tsx +++ b/source/Component/CommandBar/CommandBar.tsx @@ -15,7 +15,7 @@ interface ICommandBarProps { } @useSetting -@useStatusWithEvent("mouseModChange") +@useStatusWithEvent("mouseModChange", "actuatorStartChange") class CommandBar extends Component { render(): ReactNode { @@ -34,7 +34,13 @@ class CommandBar extends Component
{this.getRenderButton({ iconName: "Save", i18NKey: "Command.Bar.Save.Info" })} - {this.getRenderButton({ iconName: "Play", i18NKey: "Command.Bar.Play.Info" })} + {this.getRenderButton({ + iconName: this.props.status?.actuator.start() ? "Pause" : "Play", + i18NKey: "Command.Bar.Play.Info", + click: () => this.props.status ? this.props.status.actuator.start( + !this.props.status.actuator.start() + ) : undefined + })} {this.getRenderButton({ iconName: "HandsFree", i18NKey: "Command.Bar.Drag.Info", active: mouseMod === MouseMod.Drag, diff --git a/source/Component/HeaderBar/HeaderBar.tsx b/source/Component/HeaderBar/HeaderBar.tsx index 5b51313..0eb6d2e 100644 --- a/source/Component/HeaderBar/HeaderBar.tsx +++ b/source/Component/HeaderBar/HeaderBar.tsx @@ -42,7 +42,7 @@ class HeaderBar extends Component< return (t: number) => { let newState: HeaderBarState = {} as any; newState[type] = 1 / t; - if (this.updateTime > 60) { + if (this.updateTime > 20) { this.updateTime = 0; this.setState(newState); } diff --git a/source/Context/Status.tsx b/source/Context/Status.tsx index b7d9069..91a2f6b 100644 --- a/source/Context/Status.tsx +++ b/source/Context/Status.tsx @@ -12,6 +12,7 @@ import { I18N } from "@Component/Localization/Localization"; import { superConnectWithEvent, superConnect } from "./Context"; import { PopupController } from "./Popups"; import { Behavior } from "@Model/Behavior"; +import { Actuator } from "@Model/Actuator"; function randomColor(unNormal: boolean = false) { const color = [ @@ -44,6 +45,7 @@ interface IStatusEvent { individualChange: void; behaviorChange: void; popupChange: void; + actuatorStartChange: void; } class Status extends Emitter { @@ -71,6 +73,11 @@ class Status extends Emitter { */ public model: Model = new Model(); + /** + * 执行器 + */ + public actuator: Actuator; + /** * 弹窗 */ @@ -104,8 +111,14 @@ class Status extends Emitter { public constructor() { super(); + // 初始化执行器 + this.actuator = new Actuator(this.model); + + // 执行器开启事件 + this.actuator.on("startChange", () => { this.emit("actuatorStartChange") }); + // 循环事件 - this.model.on("loop", (t) => { this.emit("physicsLoop", t) }); + this.actuator.on("loop", (t) => { this.emit("physicsLoop", t) }); // 对象变化事件 this.model.on("objectChange", () => this.emit("objectChange")); diff --git a/source/Model/Actuator.ts b/source/Model/Actuator.ts new file mode 100644 index 0000000..83ba8ad --- /dev/null +++ b/source/Model/Actuator.ts @@ -0,0 +1,125 @@ +import { Model } from "./Model"; +import { Emitter } from "./Emitter"; + +interface IActuatorEvent { + startChange: boolean; + loop: number; +} + +/** + * 模型执行器 + */ +class Actuator extends Emitter { + + /** + * 速度系数 + */ + public speed: number = 1; + + /** + * 模拟帧率 + */ + public fps: number = 24; + + /** + * 仿真是否进行 + */ + private startFlag: boolean = false; + + /** + * 主时钟状态控制 + */ + public start(start?: boolean): boolean { + if (start === undefined) { + return this.startFlag; + } else { + this.startFlag = start; + this.lastTime = 0; + this.alignTimer = 0; + this.emit("startChange", start); + return start; + } + } + + /** + * 绑定模型 + */ + public model: Model; + + /** + * 上一帧的时间 + */ + private lastTime: number = 0; + + /** + * 对其计时器 + */ + private alignTimer: number = 0; + + public tickerType: 1 | 2 = 1; + + private ticker(t: number) { + if (this.startFlag && t !== 0) { + if (this.lastTime === 0) { + this.lastTime = t; + } else { + let durTime = (t - this.lastTime) / 1000; + this.lastTime = t; + + // 丢帧判定 + if (durTime > 0.1) { + console.log("Actuator: Ticker dur time error. dropping...") + } + + this.alignTimer += durTime; + if (this.alignTimer > (1 / this.fps)) { + this.model.update(this.alignTimer * this.speed); + this.emit("loop", this.alignTimer); + this.alignTimer = 0; + } + } + } else { + this.emit("loop", Infinity); + } + } + + /** + * 帧率对其时钟 + * 1、使用 requestAnimationFrame 保证最高的性能 + * 2、最大模拟帧率只能小于 60 + * 3、可能出现帧率对其问题 + */ + private tickerAlign = (t: number) => { + this.ticker(t); + requestAnimationFrame(this.tickerAlign); + } + + /** + * 精确时钟 + */ + private tickerExp = () => { + this.ticker(window.performance.now()); + setTimeout(this.tickerExp, (1 / this.fps) * 1000); + } + + /** + * 执行器 + */ + private runTicker = (t: number) => { + if (this.tickerType === 1) { + this.ticker(t); + requestAnimationFrame(this.runTicker); + } else { + this.ticker(window.performance.now()); + setTimeout(this.runTicker, (1 / this.fps) * 1000); + } + } + + public constructor(model: Model) { + super(); + this.model = model; + this.runTicker(0); + } +} + +export { Actuator } \ No newline at end of file diff --git a/source/Model/Model.ts b/source/Model/Model.ts index a28f07b..23ad788 100644 --- a/source/Model/Model.ts +++ b/source/Model/Model.ts @@ -8,7 +8,6 @@ import { Label } from "./Label"; import { Behavior, IAnyBehavior, IAnyBehaviorRecorder } from "./Behavior"; type ModelEvent = { - loop: number; labelChange: Label[]; objectChange: CtrlObject[]; individualChange: Group; @@ -280,8 +279,6 @@ class Model extends Emitter { } this.draw(); - - this.emit("loop", t); } public draw() {