1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2024-10-27 20:34:29 +00:00

Vastly improve belt performance

This commit is contained in:
tobspr 2020-08-10 15:02:14 +02:00
parent 31eeeab8ce
commit 6f28aff78f
8 changed files with 275 additions and 196 deletions

View File

@ -1,14 +1,14 @@
import { globalConfig } from "../core/config"; import { globalConfig } from "../core/config";
import { DrawParameters } from "../core/draw_parameters"; import { DrawParameters } from "../core/draw_parameters";
import { gItemRegistry } from "../core/global_registries";
import { createLogger } from "../core/logging"; import { createLogger } from "../core/logging";
import { Rectangle } from "../core/rectangle";
import { epsilonCompare, round4Digits } from "../core/utils"; import { epsilonCompare, round4Digits } from "../core/utils";
import { Vector } from "../core/vector"; import { enumDirection, enumDirectionToVector, Vector } from "../core/vector";
import { BasicSerializableObject, types } from "../savegame/serialization";
import { BaseItem } from "./base_item"; import { BaseItem } from "./base_item";
import { Entity } from "./entity"; import { Entity } from "./entity";
import { GameRoot, enumLayer } from "./root"; import { GameRoot } from "./root";
import { Rectangle } from "../core/rectangle";
import { BasicSerializableObject, types } from "../savegame/serialization";
import { gItemRegistry } from "../core/global_registries";
const logger = createLogger("belt_path"); const logger = createLogger("belt_path");
@ -125,14 +125,6 @@ export class BeltPath extends BasicSerializableObject {
return globalConfig.beltItemSpacingByLayer[this.layer]; return globalConfig.beltItemSpacingByLayer[this.layer];
} }
/**
* Returns the layer of the this path
* @returns {enumLayer}
*/
getLayer() {
return this.entityPath[0].layer;
}
/** /**
* Tries to accept the item * Tries to accept the item
* @param {BaseItem} item * @param {BaseItem} item
@ -167,7 +159,7 @@ export class BeltPath extends BasicSerializableObject {
* @returns {BaseItem|null} * @returns {BaseItem|null}
*/ */
findItemAtTile(tile) { findItemAtTile(tile) {
// TODO: This breaks color blind mode otherwise // @TODO: This breaks color blind mode otherwise
return null; return null;
} }
@ -186,27 +178,82 @@ export class BeltPath extends BasicSerializableObject {
} }
/** /**
* Updates all ejectors on the path, so that only the last ejector * Recomputes the layer of the path and the target acceptor
*/ */
onPathChanged() { onPathChanged() {
this.ejectorComp = this.entityPath[this.entityPath.length - 1].components.ItemEjector; this.layer = this.entityPath[0].layer;
this.ejectorSlot = this.ejectorComp.slots[0]; this.acceptorTarget = this.computeAcceptingEntityAndSlot();
}
for (let i = 0; i < this.entityPath.length; ++i) { /**
const ejectorComp = this.entityPath[i].components.ItemEjector; * Called by the belt system when the surroundings changed
const isLast = i === this.entityPath.length - 1; */
ejectorComp.enabled = isLast; onSurroundingsChanged() {
this.onPathChanged();
}
// Clear all slots of non-end entities /**
if (!isLast) { * Finds the entity which accepts our items
for (let k = 0; k < ejectorComp.slots.length; ++k) { * @return {{ entity: Entity, slot: number }}
ejectorComp.slots[k].item = null; */
ejectorComp.slots[k].progress = 0.0; computeAcceptingEntityAndSlot() {
const lastEntity = this.entityPath[this.entityPath.length - 1];
const lastStatic = lastEntity.components.StaticMapEntity;
const lastBeltComp = lastEntity.components.Belt;
// Figure out where and into which direction we eject items
const ejectSlotWsTile = lastStatic.localTileToWorld(new Vector(0, 0));
const ejectSlotWsDirection = lastStatic.localDirectionToWorld(lastBeltComp.direction);
const ejectSlotWsDirectionVector = enumDirectionToVector[ejectSlotWsDirection];
const ejectSlotTargetWsTile = ejectSlotWsTile.add(ejectSlotWsDirectionVector);
// Try to find the given acceptor component to take the item
// Since there can be cross layer dependencies, check on all layers
const targetEntities = this.root.map.getLayersContentsMultipleXY(
ejectSlotTargetWsTile.x,
ejectSlotTargetWsTile.y
);
for (let i = 0; i < targetEntities.length; ++i) {
const targetEntity = targetEntities[i];
const targetStaticComp = targetEntity.components.StaticMapEntity;
const targetBeltComp = targetEntity.components.Belt;
// Check for belts (special case)
if (targetBeltComp) {
const beltAcceptingDirection = targetStaticComp.localDirectionToWorld(enumDirection.top);
if (ejectSlotWsDirection === beltAcceptingDirection) {
return {
entity: targetEntity,
slot: 0,
};
} }
} }
}
this.layer = this.entityPath[0].layer; // Check for item acceptors
const targetAcceptorComp = targetEntity.components.ItemAcceptor;
if (!targetAcceptorComp) {
// Entity doesn't accept items
continue;
}
const matchingSlot = targetAcceptorComp.findMatchingSlot(
targetStaticComp.worldToLocalTile(ejectSlotTargetWsTile),
targetStaticComp.worldDirectionToLocal(ejectSlotWsDirection),
lastEntity.layer
);
if (!matchingSlot) {
// No matching slot found
continue;
}
return {
entity: targetEntity,
slot: matchingSlot.index,
};
}
} }
// Following code will be compiled out outside of dev versions // Following code will be compiled out outside of dev versions
@ -251,11 +298,6 @@ export class BeltPath extends BasicSerializableObject {
return fail("Reference to destroyed entity " + entity.uid); return fail("Reference to destroyed entity " + entity.uid);
} }
const enabledState = i === this.entityPath.length - 1;
if (entity.components.ItemEjector.enabled !== enabledState) {
return fail("Item ejector enabled state is not synchronized (index =" + i + ")");
}
const followUp = this.root.systemMgr.systems.belt.findFollowUpEntity(entity); const followUp = this.root.systemMgr.systems.belt.findFollowUpEntity(entity);
if (!followUp) { if (!followUp) {
return fail( return fail(
@ -283,20 +325,6 @@ export class BeltPath extends BasicSerializableObject {
} }
} }
// Check for right ejector component and slot
if (this.ejectorComp !== this.entityPath[this.entityPath.length - 1].components.ItemEjector) {
return fail("Stale ejectorComp handle");
}
if (this.ejectorSlot !== this.ejectorComp.slots[0]) {
return fail("Stale ejector slot handle");
}
if (!this.ejectorComp) {
return fail("Ejector comp not set");
}
if (!this.ejectorSlot) {
return fail("Ejector slot not set");
}
// Check spacing // Check spacing
if (this.spacingToFirstItem > this.totalLength + 0.005) { if (this.spacingToFirstItem > this.totalLength + 0.005) {
return fail( return fail(
@ -370,14 +398,6 @@ export class BeltPath extends BasicSerializableObject {
const beltComp = entity.components.Belt; const beltComp = entity.components.Belt;
// If the last belt has something on its ejector, put that into the path first
const pendingItem = this.ejectorComp.takeSlotItem(0);
if (pendingItem) {
// Ok, so we have a pending item
DEBUG && logger.log("Taking pending item and putting it back on the path");
this.items.push([0, pendingItem]);
}
// Append the entity // Append the entity
this.entityPath.push(entity); this.entityPath.push(entity);
this.onPathChanged(); this.onPathChanged();
@ -970,7 +990,7 @@ export class BeltPath extends BasicSerializableObject {
beltSpeed *= 100; beltSpeed *= 100;
} }
let minimumDistance = this.ejectorSlot.item ? this.getItemSpacing() : 0; let minimumDistance = 0;
// Try to reduce spacing // Try to reduce spacing
let remainingAmount = beltSpeed; let remainingAmount = beltSpeed;
@ -994,10 +1014,17 @@ export class BeltPath extends BasicSerializableObject {
minimumDistance = this.getItemSpacing(); minimumDistance = this.getItemSpacing();
} }
// Check if we have an item which is ready to be emitted
const lastItem = this.items[this.items.length - 1]; const lastItem = this.items[this.items.length - 1];
if (lastItem && lastItem[_nextDistance] === 0) { if (lastItem && lastItem[_nextDistance] === 0 && this.acceptorTarget) {
// Take over // Pass over the item
if (this.ejectorComp.tryEject(0, lastItem[_item])) { if (
this.root.systemMgr.systems.itemEjector.tryPassOverItem(
lastItem[_item],
this.acceptorTarget.entity,
this.acceptorTarget.slot
)
) {
this.items.pop(); this.items.pop();
} }
} }
@ -1134,7 +1161,7 @@ export class BeltPath extends BasicSerializableObject {
const beltLength = beltComp.getEffectiveLengthTiles(this.layer); const beltLength = beltComp.getEffectiveLengthTiles(this.layer);
// Check if the current items are on the belt // Check if the current items are on the belt
while (trackPos + beltLength >= currentItemPos) { while (trackPos + beltLength >= currentItemPos - 1e-51) {
// Its on the belt, render it now // Its on the belt, render it now
const staticComp = entity.components.StaticMapEntity; const staticComp = entity.components.StaticMapEntity;
assert( assert(

View File

@ -1,5 +1,7 @@
import { MetaBuilding, defaultBuildingVariant } from "./meta_building"; /* typehints:start */
import { MetaBuilding } from "./meta_building";
import { AtlasSprite } from "../core/sprites"; import { AtlasSprite } from "../core/sprites";
/* typehints:end */
/** /**
* @typedef {{ * @typedef {{
@ -22,13 +24,18 @@ export const gBuildingVariants = {
}; };
/** /**
* * Registers a new variant
* @param {*} id * @param {number} id
* @param {*} meta * @param {typeof MetaBuilding} meta
* @param {*} variant * @param {string} variant
* @param {*} rotationVariant * @param {number} rotationVariant
*/ */
export function registerBuildingVariant(id, meta, variant = defaultBuildingVariant, rotationVariant = 0) { export function registerBuildingVariant(
id,
meta,
variant = "default" /* FIXME: Circular dependency, actually its defaultBuildingVariant */,
rotationVariant = 0
) {
assert(!gBuildingVariants[id], "Duplicate id: " + id); assert(!gBuildingVariants[id], "Duplicate id: " + id);
gBuildingVariants[id] = { gBuildingVariants[id] = {
metaClass: meta, metaClass: meta,

View File

@ -53,34 +53,8 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
direction: enumDirection.top, // updated later direction: enumDirection.top, // updated later
}) })
); );
// Make this entity replaceabel // Make this entity replaceable
entity.addComponent(new ReplaceableMapEntityComponent()); entity.addComponent(new ReplaceableMapEntityComponent());
entity.addComponent(
new ItemAcceptorComponent({
slots: [
{
pos: new Vector(0, 0),
directions: [enumDirection.bottom],
layer: this.getLayer(),
},
],
animated: false,
})
);
entity.addComponent(
new ItemEjectorComponent({
slots: [
{
pos: new Vector(0, 0),
direction: enumDirection.top, // updated later
layer: this.getLayer(),
},
],
instantEject: true,
})
);
} }
/** /**
@ -90,7 +64,6 @@ export class MetaBeltBaseBuilding extends MetaBuilding {
*/ */
updateVariants(entity, rotationVariant) { updateVariants(entity, rotationVariant) {
entity.components.Belt.direction = arrayBeltVariantToRotation[rotationVariant]; entity.components.Belt.direction = arrayBeltVariantToRotation[rotationVariant];
entity.components.ItemEjector.slots[0].direction = arrayBeltVariantToRotation[rotationVariant];
} }
/** /**

View File

@ -7,6 +7,40 @@ import { enumLayer } from "../root";
export const curvedBeltLength = /* Math.PI / 4 */ 0.78; export const curvedBeltLength = /* Math.PI / 4 */ 0.78;
/** @type {import("./item_acceptor").ItemAcceptorSlot} */
export const FAKE_BELT_ACCEPTOR_SLOT = {
pos: new Vector(0, 0),
directions: [enumDirection.bottom],
layer: enumLayer.regular,
};
/** @type {Object<enumDirection, import("./item_ejector").ItemEjectorSlot>} */
export const FAKE_BELT_EJECTOR_SLOT_BY_DIRECTION = {
[enumDirection.top]: {
pos: new Vector(0, 0),
direction: enumDirection.top,
item: null,
layer: enumLayer.regular,
progress: 0,
},
[enumDirection.right]: {
pos: new Vector(0, 0),
direction: enumDirection.right,
item: null,
layer: enumLayer.regular,
progress: 0,
},
[enumDirection.left]: {
pos: new Vector(0, 0),
direction: enumDirection.left,
item: null,
layer: enumLayer.regular,
progress: 0,
},
};
export class BeltComponent extends Component { export class BeltComponent extends Component {
static getId() { static getId() {
return "Belt"; return "Belt";
@ -56,6 +90,27 @@ export class BeltComponent extends Component {
return this.direction === enumDirection.top ? 1.0 : curvedBeltLength; return this.direction === enumDirection.top ? 1.0 : curvedBeltLength;
} }
/**
* Returns fake acceptor slot used for matching
* @returns {import("./item_acceptor").ItemAcceptorSlot}
*/
getFakeAcceptorSlot() {
return FAKE_BELT_ACCEPTOR_SLOT;
}
/**
* Returns fake acceptor slot used for matching
* @returns {import("./item_ejector").ItemEjectorSlot}
*/
getFakeEjectorSlots() {
assert(
FAKE_BELT_EJECTOR_SLOT_BY_DIRECTION[this.direction],
"Invalid belt direction: ",
this.direction
);
return FAKE_BELT_EJECTOR_SLOT_BY_DIRECTION[this.direction];
}
/** /**
* Converts from belt space (0 = start of belt ... 1 = end of belt) to the local * Converts from belt space (0 = start of belt ... 1 = end of belt) to the local
* belt coordinates (-0.5|-0.5 to 0.5|0.5) * belt coordinates (-0.5|-0.5 to 0.5|0.5)

View File

@ -5,6 +5,7 @@ import { types } from "../../savegame/serialization";
import { gItemRegistry } from "../../core/global_registries"; import { gItemRegistry } from "../../core/global_registries";
import { Entity } from "../entity"; import { Entity } from "../entity";
import { enumLayer } from "../root"; import { enumLayer } from "../root";
import { BeltPath } from "../belt_path";
/** /**
* @typedef {{ * @typedef {{
@ -14,6 +15,7 @@ import { enumLayer } from "../root";
* layer: enumLayer, * layer: enumLayer,
* progress: number?, * progress: number?,
* cachedDestSlot?: import("./item_acceptor").ItemAcceptorLocatedSlot, * cachedDestSlot?: import("./item_acceptor").ItemAcceptorLocatedSlot,
* cachedBeltPath?: BeltPath,
* cachedTargetEntity?: Entity * cachedTargetEntity?: Entity
* }} ItemEjectorSlot * }} ItemEjectorSlot
*/ */
@ -24,7 +26,7 @@ export class ItemEjectorComponent extends Component {
} }
static getSchema() { static getSchema() {
// The cachedDestSlot, cachedTargetEntity, and cachedConnectedSlots fields // The cachedDestSlot, cachedTargetEntity fields
// are not serialized. // are not serialized.
return { return {
instantEject: types.bool, instantEject: types.bool,
@ -73,9 +75,6 @@ export class ItemEjectorComponent extends Component {
this.setSlots(slots); this.setSlots(slots);
/** @type {ItemEjectorSlot[]} */
this.cachedConnectedSlots = null;
/** /**
* Whether this ejector slot is enabled * Whether this ejector slot is enabled
*/ */

View File

@ -198,50 +198,66 @@ export class GameLogic {
for (let i = 0; i < entities.length; ++i) { for (let i = 0; i < entities.length; ++i) {
const entity = entities[i]; const entity = entities[i];
let ejectorSlots = [];
let acceptorSlots = [];
const staticComp = entity.components.StaticMapEntity; const staticComp = entity.components.StaticMapEntity;
const itemEjector = entity.components.ItemEjector; const itemEjector = entity.components.ItemEjector;
const itemAcceptor = entity.components.ItemAcceptor;
const beltComp = entity.components.Belt;
if (itemEjector) { if (itemEjector) {
for (let ejectorSlot = 0; ejectorSlot < itemEjector.slots.length; ++ejectorSlot) { ejectorSlots = itemEjector.slots.slice();
const slot = itemEjector.slots[ejectorSlot]; }
if (slot.layer !== layer) {
continue; if (itemAcceptor) {
} acceptorSlots = itemAcceptor.slots.slice();
const wsTile = staticComp.localTileToWorld(slot.pos); }
const wsDirection = staticComp.localDirectionToWorld(slot.direction);
const targetTile = wsTile.add(enumDirectionToVector[wsDirection]); if (beltComp) {
if (targetTile.equals(tile)) { const fakeEjectorSlot = beltComp.getFakeEjectorSlots();
ejectors.push({ const fakeAcceptorSlot = beltComp.getFakeAcceptorSlot();
entity, ejectorSlots.push(fakeEjectorSlot);
slot, acceptorSlots.push(fakeAcceptorSlot);
fromTile: wsTile, }
toDirection: wsDirection,
}); for (let ejectorSlot = 0; ejectorSlot < ejectorSlots.length; ++ejectorSlot) {
} const slot = ejectorSlots[ejectorSlot];
if (slot.layer !== layer) {
continue;
}
const wsTile = staticComp.localTileToWorld(slot.pos);
const wsDirection = staticComp.localDirectionToWorld(slot.direction);
const targetTile = wsTile.add(enumDirectionToVector[wsDirection]);
if (targetTile.equals(tile)) {
ejectors.push({
entity,
slot,
fromTile: wsTile,
toDirection: wsDirection,
});
} }
} }
const itemAcceptor = entity.components.ItemAcceptor; for (let acceptorSlot = 0; acceptorSlot < acceptorSlots.length; ++acceptorSlot) {
if (itemAcceptor) { const slot = acceptorSlots[acceptorSlot];
for (let acceptorSlot = 0; acceptorSlot < itemAcceptor.slots.length; ++acceptorSlot) { if (slot.layer !== layer) {
const slot = itemAcceptor.slots[acceptorSlot]; continue;
if (slot.layer !== layer) { }
continue;
}
const wsTile = staticComp.localTileToWorld(slot.pos); const wsTile = staticComp.localTileToWorld(slot.pos);
for (let k = 0; k < slot.directions.length; ++k) { for (let k = 0; k < slot.directions.length; ++k) {
const direction = slot.directions[k]; const direction = slot.directions[k];
const wsDirection = staticComp.localDirectionToWorld(direction); const wsDirection = staticComp.localDirectionToWorld(direction);
const sourceTile = wsTile.add(enumDirectionToVector[wsDirection]); const sourceTile = wsTile.add(enumDirectionToVector[wsDirection]);
if (sourceTile.equals(tile)) { if (sourceTile.equals(tile)) {
acceptors.push({ acceptors.push({
entity, entity,
slot, slot,
toTile: wsTile, toTile: wsTile,
fromDirection: wsDirection, fromDirection: wsDirection,
}); });
}
} }
} }
} }

View File

@ -137,6 +137,9 @@ export class BeltSystem extends GameSystemWithFilter {
const originalRect = staticComp.getTileSpaceBounds(); const originalRect = staticComp.getTileSpaceBounds();
const affectedArea = originalRect.expandedInAllDirections(1); const affectedArea = originalRect.expandedInAllDirections(1);
/** @type {Set<BeltPath>} */
const changedPaths = new Set();
for (let x = affectedArea.x; x < affectedArea.right(); ++x) { for (let x = affectedArea.x; x < affectedArea.right(); ++x) {
for (let y = affectedArea.y; y < affectedArea.bottom(); ++y) { for (let y = affectedArea.y; y < affectedArea.bottom(); ++y) {
if (originalRect.containsPoint(x, y)) { if (originalRect.containsPoint(x, y)) {
@ -189,10 +192,17 @@ export class BeltSystem extends GameSystemWithFilter {
// Make sure the chunks know about the update // Make sure the chunks know about the update
this.root.signals.entityChanged.dispatch(targetEntity); this.root.signals.entityChanged.dispatch(targetEntity);
} }
if (targetBeltComp.assignedPath) {
changedPaths.add(targetBeltComp.assignedPath);
}
} }
} }
} }
// notify all paths *afterwards* to avoid multi-updates
changedPaths.forEach(path => path.onSurroundingsChanged());
if (G_IS_DEV && globalConfig.debug.checkBeltPaths) { if (G_IS_DEV && globalConfig.debug.checkBeltPaths) {
this.debug_verifyBeltPaths(); this.debug_verifyBeltPaths();
} }
@ -361,24 +371,10 @@ export class BeltSystem extends GameSystemWithFilter {
const followUpBeltComp = followUpEntity.components.Belt; const followUpBeltComp = followUpEntity.components.Belt;
if (followUpBeltComp) { if (followUpBeltComp) {
const followUpStatic = followUpEntity.components.StaticMapEntity; const followUpStatic = followUpEntity.components.StaticMapEntity;
const followUpAcceptor = followUpEntity.components.ItemAcceptor;
// Check if the belt accepts items from our direction const acceptedDirection = followUpStatic.localDirectionToWorld(enumDirection.top);
const acceptorSlots = followUpAcceptor.slots; if (acceptedDirection === followUpDirection) {
for (let i = 0; i < acceptorSlots.length; ++i) { return followUpEntity;
const slot = acceptorSlots[i];
// Make sure the acceptor slot is on the same layer
if (slot.layer !== entity.layer) {
continue;
}
for (let k = 0; k < slot.directions.length; ++k) {
const localDirection = followUpStatic.localDirectionToWorld(slot.directions[k]);
if (enumInvertedDirections[localDirection] === followUpDirection) {
return followUpEntity;
}
}
} }
} }
} }
@ -405,21 +401,12 @@ export class BeltSystem extends GameSystemWithFilter {
const supplyBeltComp = supplyEntity.components.Belt; const supplyBeltComp = supplyEntity.components.Belt;
if (supplyBeltComp) { if (supplyBeltComp) {
const supplyStatic = supplyEntity.components.StaticMapEntity; const supplyStatic = supplyEntity.components.StaticMapEntity;
const supplyEjector = supplyEntity.components.ItemEjector; const otherDirection = supplyStatic.localDirectionToWorld(
enumInvertedDirections[supplyBeltComp.direction]
);
// Check if the belt accepts items from our direction if (otherDirection === supplyDirection) {
const ejectorSlots = supplyEjector.slots; return supplyEntity;
for (let i = 0; i < ejectorSlots.length; ++i) {
const slot = ejectorSlots[i];
// Make sure the ejector slot is on the same layer
if (slot.layer !== entity.layer) {
continue;
}
const localDirection = supplyStatic.localDirectionToWorld(slot.direction);
if (enumInvertedDirections[localDirection] === supplyDirection) {
return supplyEntity;
}
} }
} }
} }

View File

@ -2,8 +2,8 @@ import { globalConfig } from "../../core/config";
import { DrawParameters } from "../../core/draw_parameters"; import { DrawParameters } from "../../core/draw_parameters";
import { createLogger } from "../../core/logging"; import { createLogger } from "../../core/logging";
import { Rectangle } from "../../core/rectangle"; import { Rectangle } from "../../core/rectangle";
import { enumDirectionToVector, Vector } from "../../core/vector"; import { enumDirection, enumDirectionToVector, Vector } from "../../core/vector";
import { BaseItem, enumItemType, enumItemTypeToLayer } from "../base_item"; import { BaseItem, enumItemTypeToLayer } from "../base_item";
import { ItemEjectorComponent } from "../components/item_ejector"; import { ItemEjectorComponent } from "../components/item_ejector";
import { Entity } from "../entity"; import { Entity } from "../entity";
import { GameSystemWithFilter } from "../game_system_with_filter"; import { GameSystemWithFilter } from "../game_system_with_filter";
@ -120,15 +120,13 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
const ejectorComp = entity.components.ItemEjector; const ejectorComp = entity.components.ItemEjector;
const staticComp = entity.components.StaticMapEntity; const staticComp = entity.components.StaticMapEntity;
// Clear the old cache. for (let slotIndex = 0; slotIndex < ejectorComp.slots.length; ++slotIndex) {
ejectorComp.cachedConnectedSlots = null; const ejectorSlot = ejectorComp.slots[slotIndex];
for (let ejectorSlotIndex = 0; ejectorSlotIndex < ejectorComp.slots.length; ++ejectorSlotIndex) {
const ejectorSlot = ejectorComp.slots[ejectorSlotIndex];
// Clear the old cache. // Clear the old cache.
ejectorSlot.cachedDestSlot = null; ejectorSlot.cachedDestSlot = null;
ejectorSlot.cachedTargetEntity = null; ejectorSlot.cachedTargetEntity = null;
ejectorSlot.cachedBeltPath = null;
// Figure out where and into which direction we eject items // Figure out where and into which direction we eject items
const ejectSlotWsTile = staticComp.localTileToWorld(ejectorSlot.pos); const ejectSlotWsTile = staticComp.localTileToWorld(ejectorSlot.pos);
@ -146,8 +144,21 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
for (let i = 0; i < targetEntities.length; ++i) { for (let i = 0; i < targetEntities.length; ++i) {
const targetEntity = targetEntities[i]; const targetEntity = targetEntities[i];
const targetAcceptorComp = targetEntity.components.ItemAcceptor;
const targetStaticComp = targetEntity.components.StaticMapEntity; const targetStaticComp = targetEntity.components.StaticMapEntity;
const targetBeltComp = targetEntity.components.Belt;
// Check for belts (special case)
if (targetBeltComp) {
const beltAcceptingDirection = targetStaticComp.localDirectionToWorld(enumDirection.top);
if (ejectSlotWsDirection === beltAcceptingDirection) {
ejectorSlot.cachedTargetEntity = targetEntity;
ejectorSlot.cachedBeltPath = targetBeltComp.assignedPath;
break;
}
}
// Check for item acceptors
const targetAcceptorComp = targetEntity.components.ItemAcceptor;
if (!targetAcceptorComp) { if (!targetAcceptorComp) {
// Entity doesn't accept items // Entity doesn't accept items
continue; continue;
@ -164,13 +175,6 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
continue; continue;
} }
// Ok we found a connection
if (ejectorComp.cachedConnectedSlots) {
ejectorComp.cachedConnectedSlots.push(ejectorSlot);
} else {
ejectorComp.cachedConnectedSlots = [ejectorSlot];
}
// A slot can always be connected to one other slot only // A slot can always be connected to one other slot only
ejectorSlot.cachedTargetEntity = targetEntity; ejectorSlot.cachedTargetEntity = targetEntity;
ejectorSlot.cachedDestSlot = matchingSlot; ejectorSlot.cachedDestSlot = matchingSlot;
@ -199,11 +203,7 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
continue; continue;
} }
if (!sourceEjectorComp.cachedConnectedSlots) { const slots = sourceEjectorComp.slots;
continue;
}
const slots = sourceEjectorComp.cachedConnectedSlots;
for (let j = 0; j < slots.length; ++j) { for (let j = 0; j < slots.length; ++j) {
const sourceSlot = slots[j]; const sourceSlot = slots[j];
const item = sourceSlot.item; const item = sourceSlot.item;
@ -212,7 +212,6 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
continue; continue;
} }
const destSlot = sourceSlot.cachedDestSlot;
const targetEntity = sourceSlot.cachedTargetEntity; const targetEntity = sourceSlot.cachedTargetEntity;
// Advance items on the slot // Advance items on the slot
@ -229,18 +228,34 @@ export class ItemEjectorSystem extends GameSystemWithFilter {
continue; continue;
} }
// Check if the target acceptor can actually accept this item // Check if we are ejecting to a belt path
const targetAcceptorComp = targetEntity.components.ItemAcceptor; const destPath = sourceSlot.cachedBeltPath;
if (!targetAcceptorComp.canAcceptItem(destSlot.index, item)) { if (destPath) {
// Try passing the item over
if (destPath.tryAcceptItem(item)) {
sourceSlot.item = null;
}
// Always stop here, since there can *either* be a belt path *or*
// a slot
continue; continue;
} }
// Try to hand over the item // Check if the target acceptor can actually accept this item
if (this.tryPassOverItem(item, targetEntity, destSlot.index)) { const destSlot = sourceSlot.cachedDestSlot;
// Handover successful, clear slot if (destSlot) {
targetAcceptorComp.onItemAccepted(destSlot.index, destSlot.acceptedDirection, item); const targetAcceptorComp = targetEntity.components.ItemAcceptor;
sourceSlot.item = null; if (!targetAcceptorComp.canAcceptItem(destSlot.index, item)) {
continue; continue;
}
// Try to hand over the item
if (this.tryPassOverItem(item, targetEntity, destSlot.index)) {
// Handover successful, clear slot
targetAcceptorComp.onItemAccepted(destSlot.index, destSlot.acceptedDirection, item);
sourceSlot.item = null;
continue;
}
} }
} }
} }