Runtime Carving
Poseidon was originally created to be an editor-time tool for designers to quickly create levels and modify them dynamically. Due to significant requests, we rewrote our core algorithm years ago to support carving at runtime. As of 3.0, runtime carving has grown up quite a bit.
One of our founding principles has always been: “Using Poseidon in your game should not make your performance worse.” So everything on this page is strictly opt-in. Poseidon does nothing at runtime, not a single tick, unless you explicitly turn it on.
There are now two ways to carve at runtime:
- The Runtime Service (new in 3.0, recommended): you tell Poseidon what changed, and it handles the queueing, neighbor tracking, and recarving for you. There’s even a drop-on component (
PoseidonRuntimeWatcher) that detects changes automatically. - Manual Operations (the original API): you build a
PoseidonOperationyourself and pump it every frame. Maximum control, maximum responsibility.
Either way, carving is a frame-divided operation. We’re crunching tons of numbers and comparing numerous triangles, but we only do a certain amount of work per frame until we’ve hit a time budget that you decide. (Moving this onto the Unity Job System is still on our roadmap.)
The Runtime Service
Step 0: Enabling the Runtime Loop
The service needs a tick to run on, and remember: we refuse to cost you anything unless you opt in. So the Poseidon runtime loop must be installed into Unity’s Player Loop, in one of three ways:
- The config asset: Poseidon ships with a
PoseidonRuntimeConfigasset in its Resources folder, with the runtime loop turned off. FlipEnable Runtime Loopon and you’re done. - From code: call
PoseidonRuntimeLoop.Install()from your own initialization code, if you prefer explicit control. - The lazy way: add
POSEIDON_RUNTIME_AUTO_INSTALLto your Scripting Define Symbols and skip all the config stuff.
The loop uninstalls itself automatically when play mode exits, and it’s fully robust to “Enter Play Mode without Domain Reload”.
If you queue work while the loop isn’t installed, Poseidon will warn you in the console rather than silently doing nothing.
The Easy Way: PoseidonRuntimeWatcher
Add the Runtime Watcher component (Poseidon → Runtime Watcher) next to any Poseidon, and it watches for changes and queues recarves for you. Three independent trigger sources, each toggleable:
- Transform watching (on by default): samples the object’s transform and triggers a recarve when it moves, rotates, or scales. You can tune:
Sample Rate: only check every N frames (with a randomized phase so a hundred watchers don’t all fire on the same frame).Matrix Tolerance: how much change counts as “moved”, so floating-point drift doesn’t cause recarves.
- Recarve On Enable (off by default): recarve when the object is enabled. By default the very first enable (scene load / spawn) is skipped; disable
Skip Initial Enableif you want it. - Recarve On Disable (off by default): when the object is disabled or destroyed, tell its former neighbors to recarve so the hole it was carving heals shut. It’s opt-in because games often toggle huge chunks of a level for occlusion/streaming reasons, and you really don’t want that recarving the world.
The watcher also lets you pick between a blocking recarve (everything finishes on one frame) or a budgeted one (Frame Time milliseconds per frame), and exposes an On Carve Completed UnityEvent that fires when a carve that touched this Poseidon finishes, including when it was recarved because a neighbor changed.
For most games, watchers on your movable Poseidons plus the config asset is the entire integration. Check out Example Scene 8 (Runtime Auto Recarving) to see it in action.
The Service From Code
Under the watcher sits PoseidonRuntimeService, a static API you can drive directly. The core idea: multiple Poseidons might change on the same frame, so reporting a change and asking for a carve are separate calls.
// Something moved. Tell the service about it (cheap, call as often as you like):
PoseidonRuntimeService.MarkDirty(poseidon, UpdateReason.TransformChanged);
// ...and whenever you're ready, queue the carve:
var handle = PoseidonRuntimeService.QueueAsyncRecarve(new CarveParameters
{
FrameTime = 10, // ms of carve work per frame
});
Some nice properties of this system:
- Dirty objects bring their neighbors. A dirtied Poseidon is recarved along with both its former and current neighbors, so when an object moves away, the hole it left behind heals, and the place it arrived at gets carved. You no longer have to track “who was touching whom”.
- Requests merge. Only one operation runs at a time. If you queue five recarves before the next one starts, they collapse into a single operation (a blocking request wins; otherwise the smallest frame budget is kept).
QueueBlockingRecarve()completes the whole carve in a single frame, which is handy for loading screens or “the level just got generated” moments.
Carve Handles
Every queue call returns a CarveHandle, a tiny, allocation-free struct that answers “has the operation that consumed my request run yet?”
var handle = PoseidonRuntimeService.QueueAsyncRecarve(parameters);
// Poll it:
if (handle.IsDone) { /* the world reflects your changes */ }
// Or promise-style (fires immediately if already done, so there's no race):
handle.WhenCompleted(h => Debug.Log("Carved!"));
// Or in a coroutine:
yield return handle.Wait();
handle.Progress gives you 0 while pending, the operation’s weighted progress while running, and 1 when done.
Service Events and Status
PoseidonRuntimeService.CarveStarted += () => ShowSpinner();
PoseidonRuntimeService.CarveCompleted += result =>
{
// result.Succeeded, result.Duration, result.FrameCount, result.ItemCount
HideSpinner();
};
There’s also PoseidonRuntimeService.IsCarving and PoseidonRuntimeService.Progress (0..1) if you’d rather poll for a progress bar. Both events are cleared automatically at the start of each play session, so you can subscribe from anywhere without leaking across sessions.
Telling “why” apart
MarkDirty takes an UpdateReason, and that reason is delivered to the Poseidon’s OnCarveFinished callback when the carve completes. Poseidons that were swept in as neighbors carry only UpdateReason.DirtyNeighbor, so your callback can tell “I carved because I was asked to” apart from “I was collateral of a neighbor’s change.”
Manual Operations
The original v1 runtime API is still fully supported, and it’s the right tool when you want total control. For example: carving a procedurally generated level exactly once, on your schedule, with your own progress UI. The key type is the PoseidonOperation: it represents a long-running carve, you decide when it runs, and it tells you when it’s done.
The basic gist of it is:
- You find which Poseidons you would like to carve.
- You call
PoseidonRuntime.GetCarveOperationto get an operation. - You then run the operation every frame until it is finished.
- You stop running the operation when it tells you it is done.
Getting a Runtime Carve Operation
PoseidonRuntime.GetCarveOperation(IEnumerable<PoseidonBase> allPoseidons,
CarveParameters parameters) => PoseidonOperation
GetCarveOperation is the main function you need to call in order to generate a PoseidonOperation which will contain all the data for the carve. You’re then responsible for calling PoseidonOperation.RunFrame(), which will run one frame tick of the carve operation for however long was set up in the CarveParameters.
Our goal with creating this function in this way is to not force a specific way of working with the tool. The first parameter is important: allPoseidons means literally ALL Poseidons that you want included. For instance, if you’re adding a new Poseidon to the scene at runtime, you’ll still need to pass in the full list of every poseidon you care about… otherwise, those other Poseidons may not be included in the carve operation.
What if I am only trying to carve a subset of my poseidons? Should I pass in only a subset of my poseidons to allPoseidons?
Let’s say that you have a scene with 100 Poseidon objects and you’re trying to only carve 3 of them together and you don’t care about 100 other carvers in your scene. The correct approach here would be to use the Filter parameter below, and to still pass all 100 objects into allPoseidons. TECHNICALLY…, however, allPoseidons can contain only the 3 poseidons you care about… which will mean that your carve will be much faster… but be forewarned, that in those cases, your Poseidons are basically isolated in their own world and will only intersect among themselves. Using the Filter parameter below is a much better way of reducing the size of your carves.
void SomeMethodToKickOffTheCarve()
{
var operation = PoseidonRuntime.GetCarveOperation(poseidons, new CarveParameters
{
FrameTime = 30, // We give each frame 30ms to compute itself
LogResults = true, // Lets log at the end to see how long the operation took
});
// Let's save this variable off somewhere and then use it somewhere else.
}
// Now I have an operation I can use to run stuff on each frame (let's say I save it off):
void Update()
{
if(operation != null && !operation.Finished)
{
operation.RunFrame();
}
}
In the rather crude example above you can clearly see how the operation is being created and
then being invoked on an Update loop in Unity. It is imperative that you fine-tune the CarveParameters
to your particular carve to get the effect you’re looking for.
Carve Parameters
Instead of creating an API that changes constantly, we’ve decided to boil it down to a parameter class that contains most of the commonly used properties in Poseidon carves. The parameters are as follows:
FrameTime (float)
DEFAULT: float.PositiveInfinity
How much time (in milliseconds) should be devoted to the carve operation?
Since these can take a decently long amount of time, we split
the operation to only do a particular amount per frame. If you want to
run the entire carve operation at once, feel free to leave this blank
or pass in float.PositiveInfinity.
We recommend something like 15ms-20ms per frame, but if you’re running this on a loading screen or somewhere where frame rate doesn’t matter, you can pass a bigger number to the operation. You can even get the runtime system to compute the entire operation in one frame if you pass float.PositiveInfinity as the frame budget.
Basically, if you pass in a small number, it will take slightly longer to compute, but it will not eat up any of your game’s performance during the operation. If you pass in a bigger number, the game will run a bit slower while it is carving, but it will take less overall time to compute.
LogResults (bool)
DEFAULT: false
Whether or not to log information about how many Poseidons
were carved and how long the entire operation took.
AssembleTiming (AssemblePhaseTiming)
DEFAULT AssemblePhaseTiming.ImmediatelyAfterEachCarve
Let’s say you’re carving 50 objects together for the first time. If you’re using AssemblePhaseTiming.AllTogetherAtEndOfProcess,
it will calculate the final output of all 50 objects, then, at the end, it will assemble all of the Poseidons and turn them
from our internal representations of meshes into the final products all at once. You’ll basically “blink” and suddenly everything will be done after crunching through the numbers.
If you’re using AssemblePhaseTiming.ImmediatelyAfterEachCarve (default), you’ll basically see the items “pop” into completion one-by-one. As soon as we’re done carving an item, we’ll “assemble” it and turn it into a proper Unity mesh, then move on to the next item.
EnableCleanupStages (bool)
DEFAULT: false
Whether the post-carve cleanup passes (Mesh Cleanup) are allowed to run as part of this operation. At runtime you almost always want a faster carve more than a prettier wireframe, so cleanup is skipped by default, even for Poseidons that have Mesh Cleanup enabled on them. Turn this on only if you’re doing something like exporting meshes, where clean geometry genuinely matters, and budget for the extra time.
Filter (Func<PoseidonBase, bool>)
DEFAULT: (p) => true
This is arguably one of the trickier, but yet more important parameters to pass in.
By default, it says: “for each Poseidon, return true”, which basically means: go through every single
Poseidon in the allPoseidons list, and yes, I want to carve it. If you are trying to recarve your entire
set of Poseidons in your scene, then you can leave this untouched.
Use Case:
Imagine a world where you are keeping track of all of the Poseidons in your scene. The user executes some command to move one object over, which affects these two carvers. (Let’s say, the user moves a Window to the other side of the wall, the two affected carvers would be the Window and the Room The Window Was On).
// Let's pretend we have these two carvers saved off as variables
List<PoseidonBase> affectedCarvers = new List<PoseidonBase> { window, roomTheWindowWasOn };
var operation = PoseidonRuntime.GetCarveOperation(allPoseidonsInTheScene, new CarveParameters
{
Filter = (p) => affectedCarvers.Any(affected => affected == p);
});
Now, even though there may be other Poseidons in the scene, the algorithm will only
recompute the meshes for window and roomTheWindowWasOn, but will also recarve anyone
who was touching them in the scene. Since only those objects technically “changed”,
we can tell Poseidon “only these two things changed, so please, recarve them and anyone
they are now affecting”.
This means that, if the room was previously also touching a hallway, that carve will still
occur; we run our Poseidon operation as an isolated operation and don’t presume to know anything
other than what you pass into us - this is why it is important to pass in ALL poseidons for the allPoseidons parameter,
and then filter it down using the Filter.
(If all of this “keep track of what changed yourself” business sounds tedious, that’s exactly why the Runtime Service exists now. MarkDirty + QueueAsyncRecarve does all of this for you.)
Running the Operation
With manual operations, you are required to “run” the operation yourself. It is asynchronous,
but not truly async, because it requires access to certain Unity functions that must be accessed on the Main thread - for this reason, you need to “run” this every frame, but we’ve encapsulated the operation in a simple wrapper, this PoseidonOperation.
It contains a few things you can access:
bool Finished { get; }- Allows you to see if an operation is done (whether it succeeded or failed).
OperationStatus Status { get; }None,Running,Succeeded, orFailed. If an exception occurs mid-carve, the operation logs it, stops at the first error, and reportsFailedrather than throwing at you every frame.
float Progress { get; }- Returns a 0.0f - 1.0f estimated progress for the operation.
bool RunFrame()- Runs the next bit of the operation if there’s any left, and returns whether or not the operation is finished. If the operation
is already done, it will no-op and return true. The return value here can be ignored if you’re also checking
Finished.
- Runs the next bit of the operation if there’s any left, and returns whether or not the operation is finished. If the operation
is already done, it will no-op and return true. The return value here can be ignored if you’re also checking
There’s no definitively “correct” way for how you should run your Carve Operation - but we personally like two methods that we’ve used
for our own development, running it on Update or as part of a Unity Coroutine.
The Update Approach
private PoseidonOperation operation; // This is set by some other method
public Image progressMeter;
void Update()
{
// If we have a running operation, and it's not done,
// let's run a bit of it every frame.
if (operation != null && !operation.Finished)
{
operation.RunFrame();
// Do something with the progress, if you'd like, such as showing a progress bar:
progressMeter.fillAmount = operation.Progress;
return;
}
// Do other stuff in your update when the operation is
// done or not running or has never run.
...
}
The Coroutine Approach
If you want something that is a bit more “async” you can try to use Unity’s coroutines.
private void StartCarving()
{
var operation = PoseidonRuntime.GetCarveOperation(poseidons, new CarveParameters
{
FrameTime = 20
});
StartCoroutine(RunCarveOperation(operation));
}
private IEnumerator RunCarveOperation(PoseidonOperation operation)
{
while(!operation.Finished)
{
operation.RunFrame();
progressMeterImage.fillAmount = operation.Progress;
yield return null;
}
// When the operation is done, let's do some post operation logic here
...
}
An Easier Way: CarveEverything
If all this seems too complicated and you just wanna carve everything all at once you can just run:
PoseidonRuntime.CarveEverything(allPoseidons);
This… immediately carves all passed in Poseidons against one another, all on the same runtime frame. This WILL block the main thread. It’s the equivalent of doing:
var operation = PoseidonRuntime.GetCarveOperation(allPoseidons, new CarveParameters
{
FrameTime = float.PositiveInfinity,
});
operation.RunFrame();
We don’t necessarily recommend this approach, but if you’re just prototyping something and need a quick way of doing it, this might come in handy for quickly telling everything to carve and blocking until it finishes carving everything.
What’s Next?
For years, this page ended with us promising a “Runtime Change Watcher” that would keep track of what needs recarving so you didn’t have to. That’s shipped: it’s the Runtime Service and PoseidonRuntimeWatcher above, and it’s opt-in exactly the way we promised it would be.
The next big item on the runtime roadmap is moving the carving math onto the Unity Job System, which should greatly speed up how fast operations carve. Same APIs, faster crunching.