Parallel light flooding
Posted by Jeff Disher
Parallel light flooding
While thinking more about this new lighting mechanic and doing some research into the subject it looks like the common approach is a breadth-first flood-fill kind of approach. This makes sense and can be reversed when lighting is removed (and then restarted on boundaries to not "erase" what other lights have done). This still isn't ideal since it means that the light behaves like water, not like light, meaning it can go around corners and solve mazes, not just casting shadows. It will probably still give the right look, though, as it isn't like the real world has anything truly matte black so there is always some reflection.

While this algorithm is reasonably elegant and seems to perform reasonably enough (and can be optimized if need be), it does still seem rather heavy. For example, flooding an empty cuboid with 15-strength light results in 4088 block lighting updates. I suspect that this will require that the block update mechanic isn't applied for literally all block changes, but probably needs to be filtered per-aspect. That way, things like damage or light value can avoid triggering that extra work and a rule added to say that they can't be relied upon in that way. Things like block type and inventory changes will always require these updates, however.

The other problem here is related to the parallel logic executor. The lighting algorithm works as though it thinks it can modify the data store as it runs and that all updates are run sequentially. This is not possible within OctoberProject's logic executor. However, if the corresponding block update happens in the tick before the lighting update, then I think that this can be made to work.

In this case, the blocks which require lighting updates (add/remove light sources or add/remove blocks with large enough neighbour light values) can be enqueued for the following tick, per-cuboid. At the end of a tick, the thread processing the cuboid can check these lighting update lists of the current cuboid, and neighbouring cuboids which are close enough to matter, and apply them based on the stale block values from the previous tick. While this will require some redundant work at the boundaries, it should be fine (reminds me of the "tiling GPU" versus "linear GPU" rasterization strategies). Now that I am writing this out, those lighting updates could be handled parallel to the other block updates, even within a cuboid.

So, despite originally being somewhat worried about this, I think it will be ok. The only frustrating part is the 1-tick delay in lighting updates but I think that can be hidden behind some kind of animation, or something (the block/torch lights _after_ being placed and the "embers" linger for a tick after being picked up). If it really mattered, these updates could be moved into a later parallel phase of the same tick, but that would require an additional synchronization point, so I would really rather not.

Either way, it looks like the lighting updates should be doable in parallel and with only modest changes to the design.

Jeff.