Skip to content

Forklift Demo (PalletYard)

Commercial

Included in the public demo Compare editions

The forklift demo is the Pallet Yard (DES) document of the public demo project. Three autonomous forklifts (AGV stackers) take pallets from two production lines, buffer them on floor slots, deliver them to two shipping lanes and recharge on their own. It is the reference example for discrete event simulation with vehicles: you can run it, change it in the planning window and compare variants in the experiment manager.

The demo combines three kinds of building blocks, and this page shows which one does what: standard components in the GLB, a model script in the GLB, and a TypeScript plugin that holds the forklift logic.

The PalletYard demo in DES mode: two production lines with pallet scanners in the front, a one-way loop of green paths with arrows, two forklifts at the chargers with the labels 53 % and 76 %, one forklift at a shipping lane in the back, yellow floor slots inside the loop and two shipping conveyors with pallets

Open web.realvirtual.io/demo/?doc=doc_palletyard_hzmdb6&mode=des. The document id is doc_palletyard_hzmdb6, the file PalletYard.glb, and it opens in the DES workspace.

  1. Press Play. The forklifts start picking, buffering, shipping and charging.
  2. Use Fast Forward to run a whole shift in seconds.
  3. Open Statistics and Event Queue in the side toolbar to see what happens.
  4. Open Experiments (flask icon in the DES toolbar) and press Run all to compare the prepared variants.
  • Production. Two production outputs, A1 and A2, each release one pallet every 90 s, offset by 45 s (80 pallets per hour together). A pallet runs to the end of its output conveyor.
  • Pick order. A pallet that arrives at the end of an output conveyor becomes a transport order.
  • Destination. When the order is assigned, the dispatcher picks the destination: a free shipping input that is not paused, otherwise the floor slot that has been free the longest.
  • Shipping. The two shipping inputs, V1 and V2, pause regularly: every 1200 s for 300 s, offset against each other. During a pause, pallets go to the six floor slots P1 to P6 and are fetched again later.
  • Charging. Below 30 % state of charge a forklift drives to one of the chargers L1 and L2 and charges to 80 %. A wait point W in front of the chargers holds one vehicle that queues for a charger.

Every station sits at the end of a short stub off the loop. A forklift backs into the stub fork first, the dock holds it while the fork lifts or sets down, and it leaves forward. The loop is one-way, so vehicles cannot overtake each other.

The Hierarchy panel of the Pallet Yard (DES) document: under PalletYardRoot, with the component PalletYard, the placements Conveyor_A1, Pallet_A1, Conveyor_A2, Pallet_A2, Conveyor_V1, Scanner_V1, ConveyorOut_V1, Sink_V1 and the same for V2, the forklifts Stapler_1 to Stapler_3 with the component Agv, the yard modules and markings, Charger_L1 and Charger_L2, the floor slots Slot_P1 to Slot_P6, the nodes Downtime_Stapler_1 to Downtime_Stapler_3 with the component Downtime, and ShippingMonitor with the component WebComponent

The document carries almost no own geometry. Conveyors, scanners, sinks, the forklifts and the chargers are placements of library assets (Pallet Handling, Mobile Robots), materialised when the scene loads.

NodeWhat it is
PathNetworkThe one-way loop and the station stubs as path segments with zones, see Path network
PalletYardRootRoot of the yard. Its PalletYard component holds the yard configuration and the experiment fields VehicleCount, AgvMtbf, AgvMttr and AgvDowntimeStochastic
Conveyor_A1, Conveyor_A2, Pallet_A1, Pallet_A2Production outputs: conveyors and the pallet sources (Source with automatic generation switched off; the plugin’s takt releases each pallet)
Conveyor_V1/V2, Scanner_V1/V2, ConveyorOut_V1/V2, Sink_V1/V2Shipping lanes, ending in a Sink each
Stapler_1 … Stapler_3The forklifts: AGV stacker placements with the AGV component
Charger_L1, Charger_L2, Slot_P1 … Slot_P6, markingsChargers, floor slots and their floor markings
Downtime_Stapler_1 … _3One Downtime per forklift: breakdowns with MTBF 1 h and MTTR 10 min, drawn randomly from the seeded stream
ShippingMonitorThe model script described below

The document also carries the three experiment variants (2 AGVs, 4 AGVs, No downtime) in its scene data, so the experiment travels with the GLB.

Task flow in the forklift demo A takt releases a pallet at a production output. When the pallet reaches the end of the conveyor it becomes a pick order in the order pool. The dispatcher picks the destination, a free shipping input or else a floor slot, and the nearest feasible forklift whose battery allows the job, and reserves both. The job runner turns the job into tasks for the forklift: drive to the stub, back in fork first, lift the pallet in the dock, drive to the destination and set the pallet down. The forklift drives on the path network, which handles routing, zones and distance keeping. After every stay the forklift gets its next job, or goes to the wait point or a charger. Takt pallet at A1 / A2 Pick order pallet at belt end Dispatcher destination + vehicle Job runner job → AGV tasks free input, else longest-free floor slot; nearest vehicle with battery Forklift on the path network shortest route · zones · distance keeping Dock: back in, lift, out pick at output · drop at input or floor slot After every stay: next job, else wait point W or charger L1 / L2 (below 30 % charge), else cruise the loop
The dispatcher decides where a pallet goes and which forklift takes it; the job runner turns that decision into driving and handling tasks.

The forklift logic is built on the fleet layers of realvirtual WEB:

  • The path network and the AGV component do the driving: shortest route to a destination segment, zone claims at junctions and stubs, distance keeping to the vehicle ahead, reversing into a stub.
  • Tasks are the low-level command to one vehicle: drive to the end of a segment, optionally backwards, stay there, and call back on arrival and when the stay ends.
  • Docks bind a station to the end of a stub segment and own the stay: the forklift is held until the fork has finished.
  • The dispatcher keeps the order pool, chooses the destination and the vehicle, checks the battery and reserves the station.
  • The job runner executes each job as a fixed sequence: fork to approach height, back in, take or set down, drive out, fork to travel height once the stub is clear.

None of this is in the GLB. The plugin creates a dispatcher and a job runner for the yard and connects them to the scene.

ShippingMonitor is a small model script on its own node under PalletYardRoot. It only observes: it listens to both shipping sinks and publishes two values, Late shipments and Longest shipping gap. A shipment counts as late when the gap since the previous one is longer than twice the target takt of 60 s. It never changes the yard.

// ShippingMonitor - teaching example: counts late shipments of the PalletYard.
// Observes both shipping sinks; changes nothing in the model.
function setup(self) {
// All state in self.persist: snapshots and continuation restore it.
const st = self.persist({ last: null, late: 0, longest: 0 });
// Publish initial values so the KPI picker finds both rows.
self.statValue('Late shipments', 0);
self.statValue('Longest shipping gap', 0, 's');
function onShipment() {
const targetTakt = 60; // seconds between shipments we aim for
if (st.last !== null) {
const gap = self.now - st.last;
if (gap > 2 * targetTakt) st.late++;
st.longest = Math.max(st.longest, gap);
self.statValue('Late shipments', st.late);
self.statValue('Longest shipping gap', st.longest, 's');
}
st.last = self.now;
}
// Subscribe in setup() only, by placement name. Outside a DES run there is no sink to hear: stay idle.
const sinkV1 = self.sink('Sink_V1');
if (sinkV1) sinkV1.on(onShipment);
const sinkV2 = self.sink('Sink_V2');
if (sinkV2) sinkV2.on(onShipment);
return {};
}
  • Where it is attached. A WebComponent (language JavaScript) on the empty node PalletYardRoot/ShippingMonitor. Open it with the Script Editor in the side toolbar.
  • Which event. self.sink('Sink_V1').on(...) fires each time a pallet reaches that sink, at its simulated time self.now. The script subscribes in setup only, because DES restarts the script for every run.
  • State. Everything it counts lives in self.persist, so a restored snapshot continues with the right numbers.
  • Result. self.statValue publishes the values in the Scripts tab of the Statistics window. In the experiment manager, Late shipments is one of the KPI rows, so the variants are compared on it.

Like every model script, it runs after Enable scripting. See Scripting in DES for the events a script can react to and Your own KPI for turning a value into an experiment KPI.

The forklift logic itself is a model plugin in TypeScript, named after the model: the viewer build contains it under the name PalletYard, and the document PalletYard.glb loads it because its row in project.json names no scriptRef. It registers when the document opens. Model plugins are switched off in the authoring workspaces such as the layout planner; this one opts into the DES workspace, so it keeps running there. Outside DES it drives the same orders in the continuous simulation.

The entry point declares the plant: the takts of the two outputs, the shipping pauses, the retrieval rule for the floor slots, and the planning parameters.

const definition = definePlantPlugin({
apiVersion: 1,
id: 'pallet-yard',
config: {
takts: cfg.stations.filter(s => s.kind === 'pick').map((s, i) => ({
station: s.id, intervalSec: intervalOf(cfg.production, i), startSec: cfg.production.phaseSec[i],
})),
pauses: cfg.shippingPauses,
lamps: {},
},
retrieval: RETRIEVAL_RULES[cfg.retrieval ?? 'legacy-fifo'],
lampMap: DEFAULT_LAMP_MAP,
parameters: yardPlanning,
});
export const { registerModelPlugins, unregisterModelPlugins } =
plantPluginExports(definition, createYardBinder(yardPlanning));
export const runInAuthoringModes = ['des'];

The shared fleet plugin owns the lifecycle — binding at start, reset, snapshots, the switch between DES and continuous simulation. The yard adds only what is specific to it, through a binder:

The plugin addsWhat it does
Scene bindingFinds stations, forklifts, forks and docks in the scene and connects them to the DES kernel (or to the continuous simulation)
Dispatcher and job runnerCreates the yard’s dispatcher (stations, battery model, forklift energy use) and the job runner with the stub, turn point and wait point layout
Planning windowDeclares the groups Size, Production load, Stations, Shipping and Fleet and applies changed values, see Planning window
Key figuresShipped (Pal/h), Buffer peak, Blocked takts and Mean order time, under the model’s section of the Statistics window
Charger labelsThe ”⚡ n %” labels above a charger while a forklift charges
RebuildRe-composes the yard when its size or the number of forklifts changes: forklifts and their breakdown nodes are placed or removed
ProblemsReports a scene that is incomplete for the yard in the Problems panel

Two forklifts at the chargers inside the loop with the labels 52 % and 74 %, the wait point as a white line in front of them, yellow floor slots, the green one-way paths with stubs and two shipping conveyors with pallets in the back

Dispatching touches every station, every vehicle and every order at once, keeps reservations and plans the battery. That is project logic around many components, not the behaviour of one component, so it is written as project code:

  • It runs natively in the browser rather than in the script sandbox, which matters for logic that searches and computes per event.
  • The compiler checks it against the viewer’s types, and it is split into modules (layout, dispatch policy, planning, runtime).
  • It reaches the full viewer API: the planning window, the Statistics window, scene rebuilds and labels in the 3D view.

Small, local behaviour that should travel with the model and take part in snapshots stays a model script, like ShippingMonitor. See Larger logic: TypeScript project code.

Who does what in the forklift demo Three columns. Standard components in the GLB: path network, AGV forklifts, sources, conveyors and sinks, downtimes, and the experiment variants. Model script in the GLB: ShippingMonitor observes the shipping sinks, publishes late shipments and the longest gap, and keeps its state in snapshots. TypeScript plugin in the viewer build: order pool, dispatcher and job runner, planning window and key figures, charger labels, and rebuilding the yard for a new size or fleet. Standard components in the GLB Path network, zones AGV forklifts Sources, conveyors, sinks Downtime per forklift Experiment variants Model script in the GLB, sandboxed ShippingMonitor observes Sink_V1 / V2 Late shipments KPI state in snapshots changes nothing TypeScript plugin in the viewer build Orders, dispatcher Job runner, docks Planning window, key figures Charger labels Rebuild for size / fleet
The GLB describes the yard and measures it; the plugin decides what the forklifts do.

You can change the demo without touching code.

  • Number of forklifts. Set Fleet ▸ Vehicles in the planning window, or VehicleCount on the PalletYard component of PalletYardRoot in the Inspector. The value applies at the next reset: the yard is rebuilt, forklifts and their breakdown nodes are placed or removed. The maximum depends on the yard size.
  • One forklift out of service. Switch Active off on the AGV of a forklift and reset. At least one forklift must stay active.
  • Load and shipping. Production load sets the rate of each output (0 to 120 Pal/h), Shipping the pause interval and duration. Both apply live, while the simulation runs.
  • Charging. Fleet ▸ Charge threshold and Charge target apply at the next decision of a forklift.
  • Breakdowns. AgvMtbf (0 = no breakdowns), AgvMttr and AgvDowntimeStochastic on the PalletYard component.
  • Yard size. Size ▸ Module rebuilds the yard with more or fewer modules of stations and slots.

The demo document already is an experiment. The Base column has 3 forklifts with breakdowns (MTBF 3600 s, MTTR 600 s); the variants change one thing each:

VariantChange
2 AGVsVehicleCount = 2
4 AGVsVehicleCount = 4
No downtimeAgvMtbf = 0

The prepared settings run 5 runs of 8 h per column. Open Experiments, press Run all and compare the throughput: the forklifts are the bottleneck, breakdowns cost about a fifth of the throughput, and a fourth forklift does not help, because the vehicles block each other on the one-way loop. To try your own fleet size, add a variant with + New variant, add VehicleCount as a property row and set a value. See Experiments and Runs.