not bad
This commit is contained in:
parent
2741b93dea
commit
434f07cc4e
103 changed files with 8387 additions and 8152 deletions
|
|
@ -4,21 +4,8 @@
|
|||
const World = global.World;
|
||||
if (!World) throw new Error("World is not available for mixin: world_spatial_budget.js");
|
||||
|
||||
function liveCount(list) {
|
||||
let count = 0;
|
||||
for (const item of list || []) if (item && !item.dead) count += 1;
|
||||
return count;
|
||||
}
|
||||
|
||||
Object.defineProperties(World.prototype, Object.getOwnPropertyDescriptors({
|
||||
performanceLevel() {
|
||||
return 0;
|
||||
},
|
||||
|
||||
performanceQuality() {
|
||||
return { level: 0, pressure: 0, effectDensity: 1, antStride: 2, effectLimit: CONFIG.effectLimit ?? 96, itemScanLimit: 16, socialScanLimit: 8, collisionInterval: 1.8, bedConflictInterval: 3.2, statsInterval: 1.2, maxDpr: 2 };
|
||||
},
|
||||
|
||||
beginFramePerformanceBudgets() {
|
||||
this.workBudget = {
|
||||
ai: Math.max(1, Math.ceil(((this.tarinai || []).length || 1) / 2)),
|
||||
|
|
@ -54,6 +41,16 @@
|
|||
return false;
|
||||
},
|
||||
|
||||
|
||||
spatialDirtySummary() {
|
||||
return {
|
||||
staticItems: Boolean(this.spatialStaticItemsDirty),
|
||||
dynamicItems: Boolean(this.spatialDynamicItemsDirty),
|
||||
tarinai: Boolean(this.spatialTarinaiDirty),
|
||||
ants: Boolean(this.spatialAntDirty),
|
||||
};
|
||||
},
|
||||
|
||||
runtimeDiagnostics() {
|
||||
const behaviorCounts = {};
|
||||
let forcedBehaviors = 0;
|
||||
|
|
@ -63,20 +60,22 @@
|
|||
if (!t || t.dead) continue;
|
||||
liveTarinai += 1;
|
||||
const behavior = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(t) : t.behavior;
|
||||
const id = behavior?.id || t.state || "none";
|
||||
const id = behavior?.actionId || t.state || "none";
|
||||
behaviorCounts[id] = (behaviorCounts[id] || 0) + 1;
|
||||
if (behavior?.forced) forcedBehaviors += 1;
|
||||
if ((t.intentLockTimer || 0) > 0.01) lockedBehaviors += 1;
|
||||
if (typeof isTarinaiBehaviorValueForced === "function" ? isTarinaiBehaviorValueForced(behavior) : Boolean(behavior?.source === "forced" || behavior?.forcedId || behavior?.forcedRequest)) forcedBehaviors += 1;
|
||||
if ((t.behaviorLockTimer || 0) > 0.01) lockedBehaviors += 1;
|
||||
}
|
||||
return {
|
||||
spatial: {
|
||||
version: this.spatialVersion || 0,
|
||||
dirty: Boolean(this.spatialDirty),
|
||||
dirtyParts: this.spatialDirtySummary?.() || null,
|
||||
dirtyReason: this.spatialDirtyReason || this.lastSpatialDirtyReason || "",
|
||||
dirtyMarksThisFrame: this.spatialDirtyMarksThisFrame || 0,
|
||||
dirtyMarksTotal: this.spatialDirtyMarksTotal || 0,
|
||||
rebuildsThisFrame: this.spatialRebuildsThisFrame || 0,
|
||||
rebuildsTotal: this.spatialRebuildsTotal || 0,
|
||||
partialRebuildsTotal: this.spatialPartialRebuildsTotal || 0,
|
||||
lastRebuildReason: this.lastSpatialRebuildReason || "",
|
||||
},
|
||||
render: {
|
||||
|
|
@ -96,68 +95,52 @@
|
|||
locked: lockedBehaviors,
|
||||
counts: behaviorCounts,
|
||||
},
|
||||
performance: {
|
||||
runtime: {
|
||||
liveTarinai,
|
||||
fps: window.__tarinaiFps || window.TarinaiPerformance?.fps || 0,
|
||||
pressure: 0,
|
||||
level: 0,
|
||||
fps: window.__tarinaiFps || 0,
|
||||
dpr: (typeof uiCache !== "undefined" ? uiCache?.canvasDpr : window.uiCache?.canvasDpr) || 0,
|
||||
quality: null,
|
||||
},
|
||||
work: {
|
||||
budget: this.workBudget || null,
|
||||
stats: this.workStats || null,
|
||||
},
|
||||
scheduler: this._itemUpdateSchedulerStats || null,
|
||||
perf: window.TarinaiPerf?.snapshot?.() || null,
|
||||
};
|
||||
},
|
||||
|
||||
grassLoadLevel() {
|
||||
const grass = this.itemCounts?.grass || 0;
|
||||
if (grass >= 180) return 3;
|
||||
if (grass >= 120) return 2;
|
||||
if (grass >= 72) return 1;
|
||||
return 0;
|
||||
},
|
||||
|
||||
grassUpdateFactor() {
|
||||
return [1.0, 1.35, 1.85, 2.45][this.grassLoadLevel()] || 1.0;
|
||||
},
|
||||
|
||||
grassLimit() {
|
||||
const limit = Number(CONFIG.grassLimit ?? 360);
|
||||
return Number.isFinite(limit) && limit >= 0 ? Math.floor(limit) : Infinity;
|
||||
return window.TarinaiGround?.grassLimit?.(this.groundType || "soil", this.fieldType || "garden") ?? (CONFIG.grassLimit ?? 99);
|
||||
},
|
||||
|
||||
isGrassLimitExempt(item) {
|
||||
return Boolean(item && item.type === "grass" && (item.grassLimitExempt || item.manualGrass || item.placedByPlayer));
|
||||
},
|
||||
|
||||
liveGrassCount() {
|
||||
let total = 0;
|
||||
for (const it of this.items || []) {
|
||||
if (it && !it.dead && it.type === "grass" && !this.isGrassLimitExempt?.(it)) total += 1;
|
||||
}
|
||||
return total;
|
||||
},
|
||||
|
||||
canAddGrass(extra = 1) {
|
||||
const limit = this.grassLimit ? this.grassLimit() : (CONFIG.grassLimit ?? 360);
|
||||
const limit = this.grassLimit ? this.grassLimit() : (CONFIG.grassLimit ?? 99);
|
||||
if (!Number.isFinite(limit)) return true;
|
||||
return (this.liveGrassCount ? this.liveGrassCount() : 0) + Math.max(0, Number(extra) || 0) <= limit;
|
||||
const current = Math.max(0, Math.floor(Number(this.itemCounts?.grass || 0)));
|
||||
return current + Math.max(0, Number(extra) || 0) <= limit;
|
||||
},
|
||||
|
||||
sortedDrawList() {
|
||||
if (!this.drawList) this.drawList = [];
|
||||
const antCount = liveCount(this.ants);
|
||||
const tarinaiCount = liveCount(this.tarinai);
|
||||
if (this.drawListDirty || this.drawList.length !== tarinaiCount + antCount) {
|
||||
this.drawList.length = 0;
|
||||
for (const t of this.tarinai || []) if (t && !t.dead) this.drawList.push(t);
|
||||
for (const a of this.ants || []) if (a && !a.dead) this.drawList.push(a);
|
||||
this.drawList.sort((a, b) => (a.y || 0) - (b.y || 0));
|
||||
this.drawListDirty = false;
|
||||
enforceGrassLimit(reason = "grass-limit") {
|
||||
const limit = this.grassLimit ? this.grassLimit() : (CONFIG.grassLimit ?? 99);
|
||||
if (!Number.isFinite(limit)) return 0;
|
||||
this.updateItemCounts?.(`${reason}:count`);
|
||||
let kept = 0;
|
||||
let removed = 0;
|
||||
for (const it of this.items || []) {
|
||||
if (!it || it.dead || it.type !== "grass") continue;
|
||||
if (kept < limit) {
|
||||
kept += 1;
|
||||
continue;
|
||||
}
|
||||
it.amount = 0;
|
||||
it.growth = 0;
|
||||
it.grassStage = 0;
|
||||
removed += 1;
|
||||
}
|
||||
return this.drawList;
|
||||
if (removed > 0) {
|
||||
this.markItemBucketsDirty?.(reason);
|
||||
this.markSpatialDirty?.(reason);
|
||||
this.markTerrainDirty?.(reason);
|
||||
}
|
||||
return removed;
|
||||
},
|
||||
|
||||
ensureSpatial(reason = "read") {
|
||||
|
|
@ -172,7 +155,14 @@
|
|||
|
||||
nearbyItems(x, y, radius, precise = false) {
|
||||
this.ensureSpatial?.("nearby-items");
|
||||
return this.spatial.nearby(this.spatial.itemCells, x, y, radius, this.spatialItemScratch, precise);
|
||||
const out = this.spatialItemScratch || [];
|
||||
out.length = 0;
|
||||
if (this.spatial?.nearbyInto && this.spatial.staticItemCells && this.spatial.dynamicItemCells) {
|
||||
this.spatial.nearbyInto(this.spatial.staticItemCells, x, y, radius, out, precise);
|
||||
this.spatial.nearbyInto(this.spatial.dynamicItemCells, x, y, radius, out, precise);
|
||||
return out;
|
||||
}
|
||||
return this.spatial.nearby(this.spatial.itemCells, x, y, radius, out, precise);
|
||||
},
|
||||
|
||||
nearbyTarinai(x, y, radius, precise = false) {
|
||||
|
|
@ -184,28 +174,58 @@
|
|||
this.ensureSpatial?.("nearby-ants");
|
||||
return this.spatial.nearby(this.spatial.antCells, x, y, radius, this.spatialAntScratch, precise);
|
||||
},
|
||||
nearbyFood(x, y, radius, precise = false) {
|
||||
this.ensureSpatial?.("nearby-food");
|
||||
if (this.spatial?.nearbySplitInto && this.spatial.staticFoodCells && this.spatial.dynamicFoodCells) {
|
||||
return this.spatial.nearbySplitInto(this.spatial.staticFoodCells, this.spatial.dynamicFoodCells, x, y, radius, this.spatialFoodScratch, precise);
|
||||
}
|
||||
return this.spatial.nearby(this.spatial.foodCells, x, y, radius, this.spatialFoodScratch, precise);
|
||||
},
|
||||
|
||||
nearbyObstacles(x, y, radius, precise = false) {
|
||||
this.ensureSpatial?.("nearby-obstacles");
|
||||
if (this.spatial?.nearbySplitInto && this.spatial.staticObstacleCells && this.spatial.dynamicObstacleCells) {
|
||||
return this.spatial.nearbySplitInto(this.spatial.staticObstacleCells, this.spatial.dynamicObstacleCells, x, y, radius, this.spatialObstacleScratch, precise);
|
||||
}
|
||||
return this.spatial.nearby(this.spatial.obstacleCells, x, y, radius, this.spatialObstacleScratch, precise);
|
||||
},
|
||||
|
||||
nearbyFood(x, y, radius, precise = false) {
|
||||
this.ensureSpatial?.("nearby-food");
|
||||
return this.spatial.nearby(this.spatial.foodCells, x, y, radius, this.spatialFoodScratch, precise);
|
||||
},
|
||||
|
||||
nearbyHazards(x, y, radius, precise = false) {
|
||||
this.ensureSpatial?.("nearby-hazards");
|
||||
if (this.spatial?.nearbySplitInto && this.spatial.staticHazardCells && this.spatial.dynamicHazardCells) {
|
||||
return this.spatial.nearbySplitInto(this.spatial.staticHazardCells, this.spatial.dynamicHazardCells, x, y, radius, this.spatialHazardScratch, precise);
|
||||
}
|
||||
return this.spatial.nearby(this.spatial.hazardCells, x, y, radius, this.spatialHazardScratch, precise);
|
||||
},
|
||||
|
||||
rebuildSpatial(force = false, reason = "manual") {
|
||||
// Direct calls keep their old eager semantics. update() uses markSpatialDirty + ensureSpatial
|
||||
// so repeated rebuild requests in the same phase are collapsed to one rebuild.
|
||||
if (!force && this.spatialDirty === false && reason !== "manual") return false;
|
||||
this.spatial.rebuild(this.items, this.tarinai, this.ants || []);
|
||||
const profiler = window.TarinaiPerf;
|
||||
const end = profiler?.begin?.("update.spatial") || null;
|
||||
try {
|
||||
const full = reason === "manual" || reason === "reset" || !this.spatialVersion || !this.spatial?.rebuildStaticItems;
|
||||
if (full) {
|
||||
this.spatial.rebuild(this.items, this.tarinai, this.ants || []);
|
||||
} else {
|
||||
const staticDirty = Boolean(this.spatialStaticItemsDirty);
|
||||
const dynamicDirty = Boolean(this.spatialDynamicItemsDirty || staticDirty);
|
||||
const tarinaiDirty = Boolean(this.spatialTarinaiDirty);
|
||||
const antDirty = Boolean(this.spatialAntDirty);
|
||||
if (staticDirty) this.spatial.rebuildStaticItems(this.items, { rebuildCombined: false });
|
||||
if (dynamicDirty) this.spatial.rebuildDynamicItems(this.items, { rebuildCombined: false });
|
||||
if (tarinaiDirty) this.spatial.rebuildTarinai(this.tarinai);
|
||||
if (antDirty) this.spatial.rebuildAnts(this.ants || []);
|
||||
this.spatialPartialRebuildsTotal = (this.spatialPartialRebuildsTotal || 0) + 1;
|
||||
}
|
||||
} finally {
|
||||
if (end) end();
|
||||
}
|
||||
this.spatialDirty = false;
|
||||
this.spatialStaticItemsDirty = false;
|
||||
this.spatialDynamicItemsDirty = false;
|
||||
this.spatialTarinaiDirty = false;
|
||||
this.spatialAntDirty = false;
|
||||
this.spatialDirtyReason = "";
|
||||
this.spatialVersion = (this.spatialVersion || 0) + 1;
|
||||
this.spatialRebuildsThisFrame = (this.spatialRebuildsThisFrame || 0) + 1;
|
||||
|
|
@ -231,13 +251,15 @@
|
|||
|
||||
addItem(item, reason = "add-item", opts = {}) {
|
||||
if (!item) return null;
|
||||
const ignoreGrassLimit = Boolean(opts.ignoreGrassLimit || opts.manualGrass || item.grassLimitExempt || item.manualGrass || item.placedByPlayer);
|
||||
if (item.type === "grass" && !ignoreGrassLimit && !this.canAddGrass?.(1)) return null;
|
||||
if (item.type === "grass" && ignoreGrassLimit) item.grassLimitExempt = true;
|
||||
if (item.type === "grass" && !this.canAddGrass?.(1)) return null;
|
||||
item.world = this;
|
||||
this.items.push(item);
|
||||
this.markItemBucketsDirty?.(reason);
|
||||
this.markSpatialDirty?.(reason);
|
||||
if (opts.terrain !== false) this.markTerrainDirty?.(reason);
|
||||
if (opts.terrain !== false) {
|
||||
if (item.type === "grass" || item.type === "trace" || item.type === "splat") this.markTerrainDirtyAt?.(item.x, item.y, Math.max(item.r || item.radius || 24, 36), reason);
|
||||
else this.markTerrainDirty?.(reason);
|
||||
}
|
||||
if (opts.countNow !== false && item.type) this.itemCounts[item.type] = (this.itemCounts[item.type] || 0) + 1;
|
||||
return item;
|
||||
},
|
||||
|
|
@ -254,15 +276,6 @@
|
|||
const bucket = this.itemTypeBuckets?.get?.(type);
|
||||
return bucket || [];
|
||||
},
|
||||
|
||||
liveItemsOfType(type, predicate = null) {
|
||||
const bucket = this.itemsOfType(type);
|
||||
if (typeof predicate !== "function") return bucket;
|
||||
const out = [];
|
||||
for (const item of bucket) if (item && !item.dead && predicate(item)) out.push(item);
|
||||
return out;
|
||||
},
|
||||
|
||||
updateItemCounts(reason = "manual") {
|
||||
const counts = this.itemCounts;
|
||||
if (!this.itemTypeBuckets) this.itemTypeBuckets = new Map();
|
||||
|
|
@ -287,6 +300,11 @@
|
|||
this.lastItemBucketRebuildReason = reason;
|
||||
},
|
||||
|
||||
liveItemsOfType(type, predicate = null) {
|
||||
const list = this.itemsOfType ? this.itemsOfType(type) : (this.items || []).filter(it => it && !it.dead && it.type === type);
|
||||
return predicate ? list.filter(predicate) : list;
|
||||
},
|
||||
|
||||
updateEffectCounts() {
|
||||
const counts = this.effectCounts;
|
||||
for (const key of Object.keys(counts)) counts[key] = 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue