Technical Details - The Logistics Network


How the logistics network works:

All Pawns (Towers are Pawns as well as Enemies) have a ResourceContainer, which contains things references to PawnResources, which contain things like Energy/Ammunition/Health/Armor/etc. Health and Armor are treated just like any other resource in this context.

Towers can request resources via the ResourceRequester component. 

The RequestResources method raises a mutable ResourceRequestEvent

public struct ResourceRequestEvent : IEvent {
    public IResourceRequester Requester;
    public ResourceTypes Type;
    public int AmountRequested;
    public List<(ResourceProvider provider, int amount)> PendingProviders;
    public ResourceRequestEvent(IResourceRequester requester, ResourceTypes resourceType, int amountRequested) {
        Requester = requester;
        Type = resourceType;
        AmountRequested = amountRequested;
        PendingProviders = new List<(ResourceProvider provider, int amount)>();
    }
}

ResourceProviders listen for this event and pledge resource to the event by adding themselves to a list of pending providers in the eventData itself.

Because the message bus is synchronous, all providers are immediately ready in the list of pending providers. The request is then queued by the ResourceDispatcher. The Dispatcher contains two ResourceDictionaries dictionaries, AwaitingPaths and ReadyForSorting containing resource request events in various states. Both have IResourceRequester keys with ResourceEntry values.  When a request is queued it's added to the AwaitingPaths dictionary 

public class ResourceEntry {
    public List<ResourceProvider> providers;
    public List<Path> paths;
    public int amount;
}

Later, in Update, the dispatcher loops through each resource provider in AwaitingPaths that has pledged resources and ensures a path is possible from the requester to the provider. It then queues the path via Pathfinding Project Pro's asynchronous path construction usage. The path construction has a callback, OnPathCalculated. When these paths are calculated, the callback updates the AwaitingPaths dictionary and adds the path to the entry's list of paths.

Later, again in Update, the dispatcher checks on these. If the number of paths is equal to the number of providers, it's no longer awaiting paths and moved to ReadyForSorting. The dispatcher then calculates the total distance from provider to requester and sorts these entries based on this distance. The Dispatcher then loops through each provider and decrements the resource request event's Amount field until the request is fulfilled. Each iteration of this loop increments an IncomingAmount field on containers to ensure they know resources are coming and not to request more.

// Loop through each entry in distances, dispatching packets and decrementing amount; breaking when none left
for (int i = 0; i < distances.Count; i++) {
    var distanceEntry = distances[i];
    var targetContainer = key.Container;
    // If amount is zero, this request is complete; add it to the list for removal
    if (distanceEntry.amount <= 0) {
        keysToRemove.Add(key);
        break;
    }
    // If target container is full, this request is considered complete; add it to the list for removal
    if (targetContainer.IncomingAmount + targetContainer.CurrentAmount >= targetContainer.Capacity) {
        keysToRemove.Add(key);
        break;
    }
        // Ensure only the amount needed is sent and only up to the amount the provider has
    int amountNeeded = math.min(distanceEntry.amount, key.Container.Capacity - key.Container.IncomingAmount - key.Container.CurrentAmount);
    int amountToDispatch = math.min(distanceEntry.provider.Container.CurrentAmount, amountNeeded);
    if (amountToDispatch == 0) continue;
        // Finally, choose a provider and tell it to dispatch a packet
    distanceEntry.provider.DispatchPacket(key.Container, distanceEntry.provider.Container.Type, amountToDispatch);
        // Set the incoming amount of the requesting container so it doesn't request any more resources
    key.Container.IncomingAmount += amountToDispatch;
        // Decrement remaining amount for next loop iteration
    distanceEntry.amount -= amountToDispatch;
}


There's nothing left to do so a packet is then ordered to be dispatched by the ResourceProvider. The packet is provided the path and at the end the container gets its requested resources.

Get Hex TD

Download NowName your own price

Leave a comment

Log in with itch.io to leave a comment.