maestro refacto to grrom, getpositions actions transfered to Observer, changed to frustum, with subscription
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
import { positionAt } from '../GPS/actions/arena/worldline.js'
|
||||
import { Frustum } from './frustum.js'
|
||||
|
||||
export class RequestorRegistry {
|
||||
|
||||
constructor(reader, getNow, scanIntervalMs, onPush, debug = false) {
|
||||
this.reader = reader
|
||||
this.getNow = getNow
|
||||
this.scanIntervalMs = scanIntervalMs
|
||||
this.onPush = onPush
|
||||
this.debug = debug
|
||||
this.requestors = new Map()
|
||||
}
|
||||
|
||||
#tickTimer = null
|
||||
#scanInFlight = false
|
||||
|
||||
#adjustFrequency(requestedMs) {
|
||||
if(typeof(requestedMs) !== 'number' || Number.isNaN(requestedMs)) return(null)
|
||||
if(requestedMs < 300 || requestedMs > 10000) return(null)
|
||||
const tickMs = this.scanIntervalMs
|
||||
const ticks = Math.round(requestedMs / tickMs)
|
||||
const minTicks = Math.ceil(300 / tickMs)
|
||||
const maxTicks = Math.floor(10000 / tickMs)
|
||||
const clampedTicks = Math.max(minTicks, Math.min(maxTicks, ticks))
|
||||
return(clampedTicks * tickMs)
|
||||
}
|
||||
|
||||
async subscribeFrustum(id, { planes, frequency }) {
|
||||
if(!id || typeof(id) !== 'string') return({ ok: false, err: 'Invalid requestor id' })
|
||||
const frustum = Frustum.fromPlanes(planes)
|
||||
if(!frustum) return({ ok: false, err: 'Invalid frustum planes' })
|
||||
|
||||
const frequencyMs = this.#adjustFrequency(frequency)
|
||||
if(frequencyMs === null) {
|
||||
return({ ok: false, err: 'Invalid frequency (expected 300–10000 ms)' })
|
||||
}
|
||||
|
||||
const t = this.getNow()
|
||||
if(t === null) return({ ok: false, err: 'Simulation not live' })
|
||||
|
||||
const agents = await this.reader.loadAllAgents()
|
||||
const matching = this.#matchAgents(agents, frustum, t)
|
||||
const pushEveryNTicks = frequencyMs / this.scanIntervalMs
|
||||
|
||||
this.requestors.set(id, {
|
||||
id,
|
||||
frustum,
|
||||
tMode: 'live',
|
||||
subscription: true,
|
||||
frequencyMs,
|
||||
pushEveryNTicks,
|
||||
tickCounter: 0,
|
||||
agents: matching,
|
||||
t,
|
||||
updatedAt: Date.now(),
|
||||
})
|
||||
this.#ensureTick()
|
||||
|
||||
return({
|
||||
ok: true,
|
||||
frequency: frequencyMs,
|
||||
agents: matching,
|
||||
t,
|
||||
})
|
||||
}
|
||||
|
||||
updateRequestor(id, spec) {
|
||||
const requestor = this.requestors.get(id)
|
||||
if(!requestor) return({ ok: false, err: 'Unknown requestor' })
|
||||
if(Array.isArray(spec?.planes)) {
|
||||
const frustum = Frustum.fromPlanes(spec.planes)
|
||||
if(!frustum) return({ ok: false, err: 'Invalid frustum planes' })
|
||||
requestor.frustum = frustum
|
||||
}
|
||||
if(typeof(spec?.frequency) === 'number') {
|
||||
const frequencyMs = this.#adjustFrequency(spec.frequency)
|
||||
if(frequencyMs === null) return({ ok: false, err: 'Invalid frequency (expected 300–10000 ms)' })
|
||||
requestor.frequencyMs = frequencyMs
|
||||
requestor.pushEveryNTicks = frequencyMs / this.scanIntervalMs
|
||||
requestor.tickCounter = 0
|
||||
}
|
||||
return({ ok: true, frequency: requestor.frequencyMs })
|
||||
}
|
||||
|
||||
unregisterRequestor(id) {
|
||||
this.requestors.delete(id)
|
||||
if(this.requestors.size === 0) this.#stopTick()
|
||||
return({ ok: true })
|
||||
}
|
||||
|
||||
getRequestorAgents(id) {
|
||||
const requestor = this.requestors.get(id)
|
||||
if(!requestor) return(null)
|
||||
return({
|
||||
agents: [...requestor.agents],
|
||||
t: requestor.t,
|
||||
frequency: requestor.frequencyMs ?? null,
|
||||
updatedAt: requestor.updatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.requestors.clear()
|
||||
this.#stopTick()
|
||||
}
|
||||
|
||||
async evaluateOnce({ frustum, t }) {
|
||||
if(!frustum || !(frustum instanceof Frustum)) {
|
||||
return({ ok: false, err: 'Invalid frustum', agents: [] })
|
||||
}
|
||||
if(typeof(t) !== 'number' || Number.isNaN(t)) return({ ok: false, err: 'Invalid simulation time', agents: [] })
|
||||
const agents = await this.reader.loadAllAgents()
|
||||
const matching = this.#matchAgents(agents, frustum, t)
|
||||
return({ ok: true, agents: matching, t })
|
||||
}
|
||||
|
||||
#resolveT(requestor) {
|
||||
if(requestor.tMode === 'fixed') return(requestor.fixedT)
|
||||
return(this.getNow())
|
||||
}
|
||||
|
||||
#matchAgents(agents, frustum, t) {
|
||||
const matching = []
|
||||
for(const agent of agents.values()) {
|
||||
const position = positionAt(agent, t)
|
||||
if(!frustum.containsPoint(position)) continue
|
||||
matching.push(this.reader.buildAgentSnapshot(agent, t))
|
||||
}
|
||||
return(matching)
|
||||
}
|
||||
|
||||
#pushUpdate(requestor) {
|
||||
if(typeof(this.onPush) !== 'function') return
|
||||
this.onPush(requestor.id, {
|
||||
agents: [...requestor.agents],
|
||||
t: requestor.t,
|
||||
})
|
||||
}
|
||||
|
||||
async #scanAll() {
|
||||
if(this.#scanInFlight || this.requestors.size === 0) return
|
||||
this.#scanInFlight = true
|
||||
try {
|
||||
const agents = await this.reader.loadAllAgents()
|
||||
for(const requestor of this.requestors.values()) {
|
||||
const t = this.#resolveT(requestor)
|
||||
if(t === null) {
|
||||
requestor.agents = []
|
||||
requestor.t = null
|
||||
requestor.updatedAt = Date.now()
|
||||
continue
|
||||
}
|
||||
requestor.agents = this.#matchAgents(agents, requestor.frustum, t)
|
||||
requestor.t = t
|
||||
requestor.updatedAt = Date.now()
|
||||
|
||||
if(!requestor.subscription) continue
|
||||
|
||||
requestor.tickCounter++
|
||||
if(requestor.tickCounter >= requestor.pushEveryNTicks) {
|
||||
requestor.tickCounter = 0
|
||||
this.#pushUpdate(requestor)
|
||||
}
|
||||
}
|
||||
if(this.debug) console.log(`[Observer] Scanned ${agents.size} agent(s) for ${this.requestors.size} requestor(s)`)
|
||||
} catch(err) {
|
||||
console.error('[Observer] Requestor scan failed:', err)
|
||||
} finally {
|
||||
this.#scanInFlight = false
|
||||
}
|
||||
}
|
||||
|
||||
#ensureTick() {
|
||||
if(this.#tickTimer) return
|
||||
this.#tickTimer = setInterval(() => {
|
||||
this.#scanAll()
|
||||
}, this.scanIntervalMs)
|
||||
}
|
||||
|
||||
#stopTick() {
|
||||
if(!this.#tickTimer) return
|
||||
clearInterval(this.#tickTimer)
|
||||
this.#tickTimer = null
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user