diff --git a/src/runtime-three/runtime-project-scheduler.ts b/src/runtime-three/runtime-project-scheduler.ts deleted file mode 100644 index 19c10cd8..00000000 --- a/src/runtime-three/runtime-project-scheduler.ts +++ /dev/null @@ -1,400 +0,0 @@ -import { - applyControlEffectToResolvedState, - cloneControlEffect, - cloneRuntimeResolvedControlState, - createActorControlTargetRef, - createDefaultResolvedControlSource, - createSchedulerResolvedControlSource, - createSetActorPresenceControlEffect, - getControlEffectResolutionKey, - type ControlEffect, - type ControlTargetRef, - type RuntimeResolvedControlState, - type SetActorPresenceControlEffect -} from "../controls/control-surface"; -import { - cloneProjectScheduler, - compareProjectScheduleRoutinePriority, - isProjectScheduleRoutineActiveAt, - type ProjectScheduler, - type ProjectScheduleRoutine -} from "../scheduler/project-scheduler"; - -type ActorPresenceScheduleRoutine = ProjectScheduleRoutine & { - target: { - kind: "actor"; - actorId: string; - }; - effect: SetActorPresenceControlEffect; -}; - -export interface RuntimeResolvedActorScheduleState { - actorId: string; - hasRules: boolean; - active: boolean; - activeRoutineId: string | null; - activeRoutineTitle: string | null; - effect: SetActorPresenceControlEffect | null; -} - -export interface RuntimeResolvedScheduledControlRoutine { - routineId: string; - title: string; - target: ControlTargetRef; - effect: ControlEffect; - resolutionKey: string; -} - -export interface RuntimeResolvedProjectScheduleState { - actors: RuntimeResolvedActorScheduleState[]; - controls: RuntimeResolvedScheduledControlRoutine[]; -} - -export interface RuntimeProjectSchedulerState { - document: ProjectScheduler; - resolved: RuntimeResolvedProjectScheduleState; -} - -function cloneRuntimeResolvedActorScheduleState( - state: RuntimeResolvedActorScheduleState -): RuntimeResolvedActorScheduleState { - return { - actorId: state.actorId, - hasRules: state.hasRules, - active: state.active, - activeRoutineId: state.activeRoutineId, - activeRoutineTitle: state.activeRoutineTitle, - effect: state.effect === null ? null : cloneControlEffect(state.effect) - }; -} - -export function cloneRuntimeResolvedProjectScheduleState( - state: RuntimeResolvedProjectScheduleState -): RuntimeResolvedProjectScheduleState { - return { - actors: state.actors.map(cloneRuntimeResolvedActorScheduleState), - controls: state.controls.map((routine) => ({ - routineId: routine.routineId, - title: routine.title, - target: cloneControlEffect(routine.effect).target, - effect: cloneControlEffect(routine.effect), - resolutionKey: routine.resolutionKey - })) - }; -} - -export function createEmptyRuntimeResolvedProjectScheduleState(): RuntimeResolvedProjectScheduleState { - return { - actors: [], - controls: [] - }; -} - -export function createRuntimeProjectSchedulerState(options: { - document: ProjectScheduler; - resolved?: RuntimeResolvedProjectScheduleState; -}): RuntimeProjectSchedulerState { - return { - document: cloneProjectScheduler(options.document), - resolved: cloneRuntimeResolvedProjectScheduleState( - options.resolved ?? createEmptyRuntimeResolvedProjectScheduleState() - ) - }; -} - -function resolveActorScheduleRules( - scheduler: ProjectScheduler, - actorId: string -): ActorPresenceScheduleRoutine[] { - return Object.values(scheduler.routines) - .filter( - (routine): routine is ActorPresenceScheduleRoutine => - routine.enabled && - routine.target.kind === "actor" && - routine.target.actorId === actorId && - routine.effect.type === "setActorPresence" - ) - .sort(compareProjectScheduleRoutinePriority); -} - -export function resolveRuntimeActorScheduleState(options: { - scheduler: ProjectScheduler; - actorId: string; - dayNumber: number; - timeOfDayHours: number; -}): RuntimeResolvedActorScheduleState { - const actorRules = resolveActorScheduleRules(options.scheduler, options.actorId); - const activeRoutine = - actorRules.find((routine) => - isProjectScheduleRoutineActiveAt( - routine, - options.dayNumber, - options.timeOfDayHours - ) - ) ?? null; - - if (activeRoutine === null) { - return { - actorId: options.actorId, - hasRules: actorRules.length > 0, - active: actorRules.length === 0, - activeRoutineId: null, - activeRoutineTitle: null, - effect: null - }; - } - - return { - actorId: options.actorId, - hasRules: true, - active: activeRoutine.effect.active, - activeRoutineId: activeRoutine.id, - activeRoutineTitle: activeRoutine.title, - effect: cloneControlEffect(activeRoutine.effect) - }; -} - -export function resolveRuntimeProjectScheduleState(options: { - scheduler: ProjectScheduler; - actorIds: string[]; - dayNumber: number; - timeOfDayHours: number; -}): RuntimeResolvedProjectScheduleState { - const actorIds = [...new Set(options.actorIds)]; - - return { - actors: actorIds.map((actorId) => - resolveRuntimeActorScheduleState({ - scheduler: options.scheduler, - actorId, - dayNumber: options.dayNumber, - timeOfDayHours: options.timeOfDayHours - }) - ), - controls: resolveRuntimeScheduledControlRoutines(options) - }; -} - -function resolveRuntimeScheduledControlRoutines(options: { - scheduler: ProjectScheduler; - dayNumber: number; - timeOfDayHours: number; -}): RuntimeResolvedScheduledControlRoutine[] { - const activeRoutines = Object.values(options.scheduler.routines) - .filter((routine) => - isProjectScheduleRoutineActiveAt( - routine, - options.dayNumber, - options.timeOfDayHours - ) - ) - .sort(compareProjectScheduleRoutinePriority); - const seenResolutionKeys = new Set(); - const resolved: RuntimeResolvedScheduledControlRoutine[] = []; - - for (const routine of activeRoutines) { - if (routine.effect.type === "setActorPresence") { - continue; - } - - const resolutionKey = getControlEffectResolutionKey(routine.effect); - - if (seenResolutionKeys.has(resolutionKey)) { - continue; - } - - seenResolutionKeys.add(resolutionKey); - resolved.push({ - routineId: routine.id, - title: routine.title, - target: cloneControlEffect(routine.effect).target, - effect: cloneControlEffect(routine.effect), - resolutionKey - }); - } - - return resolved; -} - -export function applyRuntimeProjectScheduleToControlState( - resolved: RuntimeResolvedControlState, - schedule: RuntimeResolvedProjectScheduleState, - baselineResolved: RuntimeResolvedControlState = resolved -): RuntimeResolvedControlState { - let nextResolved = cloneRuntimeResolvedControlState(baselineResolved); - - for (const state of resolved.discrete) { - if ( - state.source.kind === "default" || - state.source.kind === "scheduler" || - state.type === "actorPresence" - ) { - continue; - } - - nextResolved = applyControlEffectToResolvedState( - nextResolved, - cloneDiscreteStateAsEffect(state), - state.source - ); - } - - for (const channel of resolved.channels) { - if ( - channel.source.kind === "default" || - channel.source.kind === "scheduler" - ) { - continue; - } - - nextResolved = applyChannelValueAsEffect(nextResolved, channel); - } - - nextResolved.discrete = nextResolved.discrete.filter( - (state) => state.type !== "actorPresence" - ); - - for (const actorState of schedule.actors) { - const effect = - actorState.effect ?? - createSetActorPresenceControlEffect({ - target: createActorControlTargetRef(actorState.actorId), - active: actorState.active - }); - nextResolved = applyControlEffectToResolvedState( - nextResolved, - effect, - actorState.activeRoutineId === null - ? createDefaultResolvedControlSource() - : createSchedulerResolvedControlSource(actorState.activeRoutineId) - ); - } - - for (const controlRoutine of schedule.controls) { - nextResolved = applyControlEffectToResolvedState( - nextResolved, - controlRoutine.effect, - createSchedulerResolvedControlSource(controlRoutine.routineId) - ); - } - - return nextResolved; -} - -function cloneDiscreteStateAsEffect( - state: RuntimeResolvedControlState["discrete"][number] -): ControlEffect { - switch (state.type) { - case "actorPresence": - return createSetActorPresenceControlEffect({ - target: state.target, - active: state.value - }); - case "modelVisibility": - return { - type: "setModelInstanceVisible", - target: state.target, - visible: state.value - }; - case "soundPlayback": - return state.value - ? { - type: "playSound", - target: state.target - } - : { - type: "stopSound", - target: state.target - }; - case "modelAnimationPlayback": - return state.clipName === null - ? { - type: "stopModelAnimation", - target: state.target - } - : { - type: "playModelAnimation", - target: state.target, - clipName: state.clipName, - loop: state.loop - }; - case "lightEnabled": - return { - type: "setLightEnabled", - target: state.target, - enabled: state.value - }; - case "lightColor": - return { - type: "setLightColor", - target: state.target, - colorHex: state.value - }; - case "interactionEnabled": - return { - type: "setInteractionEnabled", - target: state.target, - enabled: state.value - }; - case "ambientLightColor": - return { - type: "setAmbientLightColor", - target: state.target, - colorHex: state.value - }; - case "sunLightColor": - return { - type: "setSunLightColor", - target: state.target, - colorHex: state.value - }; - } -} - -function applyChannelValueAsEffect( - resolved: RuntimeResolvedControlState, - channel: RuntimeResolvedControlState["channels"][number] -): RuntimeResolvedControlState { - switch (channel.type) { - case "lightIntensity": - return applyControlEffectToResolvedState( - resolved, - { - type: "setLightIntensity", - target: channel.descriptor.target, - intensity: channel.value - }, - channel.source - ); - case "soundVolume": - return applyControlEffectToResolvedState( - resolved, - { - type: "setSoundVolume", - target: channel.descriptor.target, - volume: channel.value - }, - channel.source - ); - case "ambientLightIntensity": - return applyControlEffectToResolvedState( - resolved, - { - type: "setAmbientLightIntensity", - target: channel.descriptor.target, - intensity: channel.value - }, - channel.source - ); - case "sunLightIntensity": - return applyControlEffectToResolvedState( - resolved, - { - type: "setSunLightIntensity", - target: channel.descriptor.target, - intensity: channel.value - }, - channel.source - ); - } -}