DNDGamePlayer: Electron редактор сцен, презентация, упаковка electron-builder
Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
import type { EffectsEvent, EffectsState, EffectToolState } from '../../shared/types';
|
||||
|
||||
function nowMs(): number {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
function defaultTool(): EffectToolState {
|
||||
return { tool: 'fog', radiusN: 0.08, intensity: 0.6 };
|
||||
}
|
||||
|
||||
export class EffectsStore {
|
||||
private state: EffectsState = {
|
||||
revision: 1,
|
||||
serverNowMs: nowMs(),
|
||||
tool: defaultTool(),
|
||||
instances: [],
|
||||
};
|
||||
|
||||
getState(): EffectsState {
|
||||
// Всегда обновляем serverNowMs при чтении — это наш "таймкод" для рендереров.
|
||||
return { ...this.state, serverNowMs: nowMs() };
|
||||
}
|
||||
|
||||
clear(): EffectsState {
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
serverNowMs: nowMs(),
|
||||
instances: [],
|
||||
};
|
||||
return this.state;
|
||||
}
|
||||
|
||||
dispatch(event: EffectsEvent): EffectsState {
|
||||
const s = this.state;
|
||||
const next: EffectsState = applyEvent(s, event);
|
||||
this.state = next;
|
||||
return next;
|
||||
}
|
||||
|
||||
/** Удаляет истёкшие (по lifetime) эффекты, чтобы state не разрастался бесконечно. */
|
||||
pruneExpired(): boolean {
|
||||
const now = nowMs();
|
||||
const before = this.state.instances.length;
|
||||
const kept = this.state.instances.filter((i) => {
|
||||
if (i.type === 'lightning') {
|
||||
return now - i.createdAtMs < i.lifetimeMs;
|
||||
}
|
||||
if (i.type === 'scorch') {
|
||||
return now - i.createdAtMs < i.lifetimeMs;
|
||||
}
|
||||
if (i.type === 'fog') {
|
||||
if (i.lifetimeMs === null) return true;
|
||||
return now - i.createdAtMs < i.lifetimeMs;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (kept.length === before) return false;
|
||||
this.state = {
|
||||
...this.state,
|
||||
revision: this.state.revision + 1,
|
||||
serverNowMs: now,
|
||||
instances: kept,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
makeId(prefix: string): string {
|
||||
return `${prefix}_${crypto.randomBytes(6).toString('hex')}_${String(nowMs())}`;
|
||||
}
|
||||
}
|
||||
|
||||
function applyEvent(state: EffectsState, event: EffectsEvent): EffectsState {
|
||||
const bump = (patch: Omit<EffectsState, 'revision' | 'serverNowMs'>): EffectsState => ({
|
||||
...patch,
|
||||
revision: state.revision + 1,
|
||||
serverNowMs: nowMs(),
|
||||
});
|
||||
switch (event.kind) {
|
||||
case 'tool.set':
|
||||
return bump({ ...state, tool: event.tool });
|
||||
case 'instances.clear':
|
||||
return bump({ ...state, instances: [] });
|
||||
case 'instance.add':
|
||||
return bump({ ...state, instances: [...state.instances, event.instance] });
|
||||
case 'instance.remove':
|
||||
return bump({ ...state, instances: state.instances.filter((i) => i.id !== event.id) });
|
||||
default: {
|
||||
// Exhaustiveness
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const _x: never = event;
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user