2026-06-25 14:51:58 +09:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
(function (global) {
|
2026-06-25 22:30:35 +09:00
|
|
|
const REALTIME_ITEM_TYPES = new Set(["ball", "genkotsu", "firecracker", "fan", "magnet"]);
|
2026-06-25 14:51:58 +09:00
|
|
|
const PIN_ITEM_TYPES = new Set(["pushpin", "oshibyo"]);
|
|
|
|
|
const SCHEDULED_ITEM_TYPES = [
|
|
|
|
|
"ball", "genkotsu", "firecracker", "pushpin", "oshibyo",
|
|
|
|
|
"plushie", "grass_bed", "zunchi", "duplicator", "water", "trace", "splat", "ant_corpse", "food",
|
|
|
|
|
"sweet", "love_mochi", "fight_mochi", "sleep_drug", "laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug", "zunda_juice",
|
2026-06-25 22:30:35 +09:00
|
|
|
"stone", "bed", "nest_box", "ant_nest", "signboard", "fence_v", "fence_h", "bounce_fence", "bounce_fence_v", "gate_fence", "fan", "magnet",
|
2026-06-25 14:51:58 +09:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
function itemUpdateInterval(item) {
|
|
|
|
|
if (!item || item.dead) return Infinity;
|
|
|
|
|
if (REALTIME_ITEM_TYPES.has(item.type)) return 0;
|
|
|
|
|
if (PIN_ITEM_TYPES.has(item.type)) return 0;
|
|
|
|
|
if (item.isStructure && item.type === "plushie") return 0;
|
|
|
|
|
if (item.isStructure && item.type === "grass_bed") return 3.0;
|
|
|
|
|
if ((item.dropTimer || 0) > 0 || Math.hypot(item.vx || 0, item.vy || 0) > 0.08) return 0;
|
|
|
|
|
if (item.type === "zunchi") return 2.4;
|
|
|
|
|
if (item.type === "grass") return Infinity;
|
2026-06-25 17:43:24 +09:00
|
|
|
if (item.type === "duplicator") return 0.5;
|
2026-06-25 14:51:58 +09:00
|
|
|
if (item.type === "water") return 1.8;
|
|
|
|
|
if (item.type === "trace" || item.type === "splat") return 1.2;
|
|
|
|
|
if (item.type === "ant_corpse") return 3.5;
|
|
|
|
|
if (typeof isServingFoodType === "function" && isServingFoodType(item.type)) return 5.0;
|
|
|
|
|
return Infinity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 ensureBuckets(worldRef) {
|
|
|
|
|
if (worldRef?.ensureItemBuckets) worldRef.ensureItemBuckets("item-update-scheduler");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function candidateItems(worldRef) {
|
|
|
|
|
if (!worldRef?.itemsOfType) return (worldRef.items || []).filter(it => it && !it.dead && it.type !== "grass");
|
|
|
|
|
const out = [];
|
|
|
|
|
for (const type of SCHEDULED_ITEM_TYPES) {
|
|
|
|
|
const bucket = worldRef.itemsOfType(type) || [];
|
|
|
|
|
for (const it of bucket) if (it && !it.dead) out.push(it);
|
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
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)));
|
|
|
|
|
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);
|