"use strict"; (function (global) { const policy = () => global.TarinaiItemUpdatePolicy || {}; function itemUpdateInterval(item) { const interval = policy().itemUpdateInterval?.(item); return Number.isFinite(interval) ? interval : Infinity; } function candidateItems(worldRef) { if (policy().candidateItems) return policy().candidateItems(worldRef); return (worldRef?.items || []).filter(it => it && !it.dead && it.type !== "grass"); } function ensureBuckets(worldRef) { if (policy().ensureBuckets) return policy().ensureBuckets(worldRef); if (worldRef?.ensureItemBuckets) worldRef.ensureItemBuckets("item-update-scheduler"); return undefined; } function heapPush(heap, entry) { heap.push(entry); let i = heap.length - 1; while (i > 0) { const p = (i - 1) >> 1; if (heap[p].time <= entry.time) break; heap[i] = heap[p]; i = p; } heap[i] = entry; } function heapPop(heap) { if (!heap.length) return null; const root = heap[0]; const last = heap.pop(); if (heap.length && last) { let i = 0; while (true) { let c = i * 2 + 1; if (c >= heap.length) break; if (c + 1 < heap.length && heap[c + 1].time < heap[c].time) c++; if (heap[c].time >= last.time) break; heap[i] = heap[c]; i = c; } heap[i] = last; } return root; } function schedulerSignature(worldRef) { return [ (worldRef.items || []).length, worldRef.itemBucketRebuildsTotal || 0, worldRef.itemBucketsDirty ? 1 : 0, ].join(":"); } function scheduleItem(state, item, now, interval = itemUpdateInterval(item)) { if (!item || item.dead || !Number.isFinite(interval)) return false; if (interval <= 0) { state.realtime.push(item); item._schedulerRealtime = true; return true; } const token = (item._schedulerToken || 0) + 1; item._schedulerToken = token; item._schedulerRealtime = false; if (!Number.isFinite(item._schedulerLastAt)) item._schedulerLastAt = now; heapPush(state.heap, { time: now + interval, item, token }); return true; } function rebuild(worldRef, reason = "manual") { ensureBuckets(worldRef); const now = worldRef.time || 0; const state = { heap: [], realtime: [], signature: schedulerSignature(worldRef), rebuilds: ((worldRef._itemUpdateScheduler?.rebuilds || 0) + 1), reason, }; for (const item of candidateItems(worldRef)) { item._schedulerLastAt = Number.isFinite(item._schedulerLastAt) ? item._schedulerLastAt : now; scheduleItem(state, item, now, itemUpdateInterval(item)); } worldRef._itemUpdateScheduler = state; worldRef._itemUpdateRealtime = state.realtime; return state; } function ensure(worldRef) { const sig = schedulerSignature(worldRef); if (!worldRef._itemUpdateScheduler || worldRef._itemUpdateScheduler.signature !== sig) return rebuild(worldRef, "signature"); return worldRef._itemUpdateScheduler; } function runRealtime(worldRef, state, dt) { let ran = 0; const now = worldRef.time || 0; const nextRealtime = []; for (const item of state.realtime || []) { if (!item || item.dead) continue; const interval = itemUpdateInterval(item); if (!Number.isFinite(interval)) { item._schedulerRealtime = false; continue; } if (interval <= 0) { if (global.TarinaiItemRuntime?.updateOne) global.TarinaiItemRuntime.updateOne(item, dt, worldRef); else item.update(dt, worldRef); item._schedulerLastAt = now; ran += 1; if (Number.isFinite(itemUpdateInterval(item)) && itemUpdateInterval(item) <= 0) nextRealtime.push(item); else scheduleItem(state, item, now, itemUpdateInterval(item)); } else { scheduleItem(state, item, now, interval); } } state.realtime = nextRealtime; worldRef._itemUpdateRealtime = state.realtime; return ran; } function runDue(worldRef, state) { let ran = 0; const now = worldRef.time || 0; const maxDue = Math.max(24, Math.ceil((state.heap.length || 0) * 0.08)); let dueRuns = 0; while (state.heap.length && state.heap[0].time <= now + 0.0001 && dueRuns < maxDue) { const entry = heapPop(state.heap); const item = entry?.item; if (!item || item.dead || item._schedulerToken !== entry.token) continue; const interval = itemUpdateInterval(item); if (!Number.isFinite(interval)) continue; if (interval <= 0) { state.realtime.push(item); item._schedulerRealtime = true; continue; } const elapsed = Math.min(10, Math.max(0.001, now - (item._schedulerLastAt ?? entry.time - interval))); if (global.TarinaiItemRuntime?.updateOne) global.TarinaiItemRuntime.updateOne(item, elapsed, worldRef); else item.update(elapsed, worldRef); item._schedulerLastAt = now; ran += 1; dueRuns += 1; scheduleItem(state, item, now, itemUpdateInterval(item)); } return ran; } function run(worldRef, dt) { const end = global.TarinaiPerf?.begin?.("update.items") || null; try { const state = ensure(worldRef); let ran = runRealtime(worldRef, state, dt); ran += runDue(worldRef, state); state.signature = schedulerSignature(worldRef); worldRef._itemUpdateSchedulerStats = { heap: state.heap.length, realtime: state.realtime.length, rebuilds: state.rebuilds, lastReason: state.reason, lastRan: ran, }; return ran; } finally { if (end) end(); } } function trackedDynamicItems(worldRef) { return worldRef?._itemUpdateRealtime || []; } global.TarinaiItemUpdateScheduler = Object.freeze({ run, rebuild, ensure, itemUpdateInterval, trackedDynamicItems }); })(typeof window !== "undefined" ? window : globalThis);