diff --git a/include/globals.h b/include/globals.h index 7d749cf..32910a7 100644 --- a/include/globals.h +++ b/include/globals.h @@ -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; diff --git a/include/procedures.h b/include/procedures.h index b7273c8..213469d 100644 --- a/include/procedures.h +++ b/include/procedures.h @@ -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); diff --git a/src/globals.c b/src/globals.c index 1b5b462..979a0d9 100644 --- a/src/globals.c +++ b/src/globals.c @@ -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; diff --git a/src/main.c b/src/main.c index 1d8bddb..32e4bcf 100644 --- a/src/main.c +++ b/src/main.c @@ -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; diff --git a/src/procedures.c b/src/procedures.c index 59330fc..75f7c07 100644 --- a/src/procedures.c +++ b/src/procedures.c @@ -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; diff --git a/src/serialize.c b/src/serialize.c index 35ab74b..a742860 100644 --- a/src/serialize.c +++ b/src/serialize.c @@ -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 () {