deferred block updates: falling sand

This commit is contained in:
2025-09-27 17:47:28 +02:00
parent 0cffb5a8f1
commit 5fcfe94449
6 changed files with 90 additions and 27 deletions

View File

@@ -74,6 +74,10 @@
// Determines the fixed amount of memory allocated to blocks
#define MAX_BLOCK_CHANGES 20000
// How many "deferred" (happen at a later time than triggered) block updates to store.
// Determines the fixed amount of memory allocated to that list
#define MAX_DEFERRED_BLOCK_UPDATES 64
// If defined, writes and reads world data to/from disk (or flash).
// This is a synchronous operation, and can cause performance issues if
// frequent random disk access is slow. Data is still stored in and
@@ -195,6 +199,13 @@ typedef struct {
uint8_t block;
} BlockChange;
typedef struct {
short x;
short z;
uint8_t y;
uint8_t awaitTicks;
} DeferredBlockUpdate;
#pragma pack(push, 1)
typedef struct {
@@ -267,6 +278,11 @@ typedef struct {
extern BlockChange block_changes[MAX_BLOCK_CHANGES];
extern int block_changes_count;
extern DeferredBlockUpdate deferred_block_updates[MAX_DEFERRED_BLOCK_UPDATES];
extern int deferred_block_updates_count;
extern uint8_t is_processing_deferred_block_updates;
extern uint8_t had_too_many_deferred_block_updates;
extern PlayerData player_data[MAX_PLAYERS];
extern int player_data_count;

View File

@@ -44,6 +44,8 @@ void handlePlayerAction (PlayerData *player, int action, short x, short y, short
void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t face);
void checkFluidUpdate (short x, uint8_t y, short z, uint8_t block);
void processBlockUpdate (short x, uint8_t y, short z, uint8_t block);
void deferBlockUpdate (short x, uint8_t y, short z, uint8_t awaitTicks);
void spawnMob (uint8_t type, short x, uint8_t y, short z, uint8_t health);
void interactEntity (int entity_id, int interactor_id);

View File

@@ -50,6 +50,11 @@ uint16_t client_count;
BlockChange block_changes[MAX_BLOCK_CHANGES];
int block_changes_count = 0;
DeferredBlockUpdate deferred_block_updates[MAX_DEFERRED_BLOCK_UPDATES];
int deferred_block_updates_count = 0;
uint8_t is_processing_deferred_block_updates = 0;
uint8_t had_too_many_deferred_block_updates = false;
PlayerData player_data[MAX_PLAYERS];
int player_data_count = 0;

View File

@@ -595,6 +595,11 @@ int main () {
// Check if it's time to yield to the idle task
task_yield();
if (had_too_many_deferred_block_updates) {
printf("WARNING: Too many deferred block updates\n");
had_too_many_deferred_block_updates = 0;
}
// Attempt to accept a new connection
for (int i = 0; i < MAX_PLAYERS; i ++) {
if (clients[i] != -1) continue;

View File

@@ -1143,6 +1143,39 @@ void playPickupAnimation (PlayerData *player, uint16_t item, double x, double y,
}
#endif
void processBlockUpdate (short x, uint8_t y, short z, uint8_t block) {
#ifdef DO_FLUID_FLOW
checkFluidUpdate(x, y, z, block);
#endif
if (isColumnBlock(block) && y) {
uint8_t below = getBlockAt(x, y - 1, z);
// TODO: if below block breaks sand
if (isReplaceableBlock(below)) {
// TODO: drop item of below block
makeBlockChange(x, y, z, 0);
makeBlockChange(x, y - 1, z, 0);
makeBlockChange(x, y - 1, z, block);
// update this (now moved) block and the block above next tick
deferBlockUpdate(x, y - 1, z, 0);
deferBlockUpdate(x, y + 1, z, 0);
}
}
}
void deferBlockUpdate (short x, uint8_t y, short z, uint8_t awaitTicks) {
if (deferred_block_updates_count == MAX_DEFERRED_BLOCK_UPDATES) {
had_too_many_deferred_block_updates = true;
return;
}
DeferredBlockUpdate *u = &deferred_block_updates[deferred_block_updates_count ++];
u->x = x;
u->y = y;
u->z = z;
u->awaitTicks = awaitTicks + is_processing_deferred_block_updates;
}
void handlePlayerAction (PlayerData *player, int action, short x, short y, short z) {
// Re-sync slot when player drops an item
@@ -1192,29 +1225,13 @@ void handlePlayerAction (PlayerData *player, int action, short x, short y, short
givePlayerItem(player, item, 1);
}
// Update nearby fluids
// Update nearby blocks
uint8_t block_above = getBlockAt(x, y + 1, z);
#ifdef DO_FLUID_FLOW
checkFluidUpdate(x, y + 1, z, block_above);
checkFluidUpdate(x - 1, y, z, getBlockAt(x - 1, y, z));
checkFluidUpdate(x + 1, y, z, getBlockAt(x + 1, y, z));
checkFluidUpdate(x, y, z - 1, getBlockAt(x, y, z - 1));
checkFluidUpdate(x, y, z + 1, getBlockAt(x, y, z + 1));
#endif
// Check if any blocks above this should break, and if so,
// iterate upward over all blocks in the column and break them
uint8_t y_offset = 1;
while (isColumnBlock(block_above)) {
// Destroy the next block
makeBlockChange(x, y + y_offset, z, 0);
// Check for item drops *without a tool*
uint16_t item = getMiningResult(0, block_above);
if (item) givePlayerItem(player, item, 1);
// Select the next block in the column
y_offset ++;
block_above = getBlockAt(x, y + y_offset, z);
}
processBlockUpdate(x, y + 1, z, block_above);
processBlockUpdate(x - 1, y, z, getBlockAt(x - 1, y, z));
processBlockUpdate(x + 1, y, z, getBlockAt(x + 1, y, z));
processBlockUpdate(x, y, z - 1, getBlockAt(x, y, z - 1));
processBlockUpdate(x, y, z + 1, getBlockAt(x, y, z + 1));
}
void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t face) {
@@ -1361,11 +1378,11 @@ void handlePlayerUseItem (PlayerData *player, short x, short y, short z, uint8_t
if (*count == 0) *item = 0;
// Calculate fluid flow
#ifdef DO_FLUID_FLOW
checkFluidUpdate(x, y + 1, z, getBlockAt(x, y + 1, z));
checkFluidUpdate(x - 1, y, z, getBlockAt(x - 1, y, z));
checkFluidUpdate(x + 1, y, z, getBlockAt(x + 1, y, z));
checkFluidUpdate(x, y, z - 1, getBlockAt(x, y, z - 1));
checkFluidUpdate(x, y, z + 1, getBlockAt(x, y, z + 1));
processBlockUpdate(x, y + 1, z, getBlockAt(x, y + 1, z));
processBlockUpdate(x - 1, y, z, getBlockAt(x - 1, y, z));
processBlockUpdate(x + 1, y, z, getBlockAt(x + 1, y, z));
processBlockUpdate(x, y, z - 1, getBlockAt(x, y, z - 1));
processBlockUpdate(x, y, z + 1, getBlockAt(x, y, z + 1));
#endif
}
@@ -1685,6 +1702,22 @@ void handleServerTick (int64_t time_since_last_tick) {
*/
if (rng_seed == 0) rng_seed = world_seed;
// block updates might add more deferred block updates,
// so we temporarily make all new block updates add one more tick to the defer tick counter
is_processing_deferred_block_updates = 1;
int next_update_idx = 0;
for (int i = 0; i < deferred_block_updates_count; i ++) {
DeferredBlockUpdate *u = &deferred_block_updates[i];
if (u->awaitTicks) {
u->awaitTicks --;
deferred_block_updates[next_update_idx ++] = *u;
} else {
processBlockUpdate(u->x, u->y, u->z, getBlockAt(u->x, u->y, u->z));
}
}
deferred_block_updates_count = next_update_idx;
is_processing_deferred_block_updates = 0;
// Tick mob behavior
for (int i = 0; i < MAX_MOBS; i ++) {
if (mob_data[i].type == 0) continue;

View File

@@ -16,6 +16,8 @@
int64_t last_disk_sync_time = 0;
// TODO: store deferred block updates
// Restores world data from disk, or writes world file if it doesn't exist
int initSerializer () {