forked from EXTERNAL/bareiron
implement syncing world to file on disk
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ notchian
|
|||||||
bareiron
|
bareiron
|
||||||
src/registries.c
|
src/registries.c
|
||||||
include/registries.h
|
include/registries.h
|
||||||
|
*.bin
|
||||||
|
@@ -35,6 +35,14 @@
|
|||||||
// How many visited chunk coordinates to "remember"
|
// How many visited chunk coordinates to "remember"
|
||||||
// The server will not re-send chunks that the player has recently been in
|
// The server will not re-send chunks that the player has recently been in
|
||||||
#define VISITED_HISTORY 4
|
#define VISITED_HISTORY 4
|
||||||
|
// How many player-made block changes to allow
|
||||||
|
// Determines the fixed amount of memory allocated to blocks
|
||||||
|
#define MAX_BLOCK_CHANGES 20000
|
||||||
|
// If defined, writes and reads world data to/from disk (PC only).
|
||||||
|
// This is a synchronous operation, and can cause performance issues if
|
||||||
|
// frequent random disk access is slow. Data is still stored in and
|
||||||
|
// accessed from memory - reading from disk is only done on startup.
|
||||||
|
#define SYNC_WORLD_TO_DISK
|
||||||
// If defined, scales the frequency at which player movement updates are
|
// If defined, scales the frequency at which player movement updates are
|
||||||
// broadcast based on the amount of players, reducing overhead for higher
|
// broadcast based on the amount of players, reducing overhead for higher
|
||||||
// player counts. For very many players, makes movement look jittery.
|
// player counts. For very many players, makes movement look jittery.
|
||||||
@@ -137,7 +145,7 @@ typedef struct {
|
|||||||
|
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
extern BlockChange block_changes[20000];
|
extern BlockChange block_changes[MAX_BLOCK_CHANGES];
|
||||||
extern int block_changes_count;
|
extern int block_changes_count;
|
||||||
|
|
||||||
extern PlayerData player_data[MAX_PLAYERS];
|
extern PlayerData player_data[MAX_PLAYERS];
|
||||||
|
21
include/serialize.h
Normal file
21
include/serialize.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef SERIALIZE_H
|
||||||
|
#define SERIALIZE_H
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "globals.h"
|
||||||
|
|
||||||
|
#if defined(SYNC_WORLD_TO_DISK) && !defined(ESP_PLATFORM)
|
||||||
|
int initSerializer ();
|
||||||
|
void writeBlockChangesToDisk (int from, int to);
|
||||||
|
void writeChestChangesToDisk (uint8_t *storage_ptr, uint8_t slot);
|
||||||
|
void writePlayerDataToDisk ();
|
||||||
|
#else
|
||||||
|
// Define no-op placeholders for when disk syncing isn't enabled
|
||||||
|
#define writeBlockChangesToDisk(a, b)
|
||||||
|
#define writeChestChangesToDisk(a, b)
|
||||||
|
#define writePlayerDataToDisk()
|
||||||
|
#define initSerializer() 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
@@ -31,6 +31,7 @@
|
|||||||
#include "worldgen.h"
|
#include "worldgen.h"
|
||||||
#include "registries.h"
|
#include "registries.h"
|
||||||
#include "procedures.h"
|
#include "procedures.h"
|
||||||
|
#include "serialize.h"
|
||||||
|
|
||||||
void handlePacket (int client_fd, int length, int packet_id, int state) {
|
void handlePacket (int client_fd, int length, int packet_id, int state) {
|
||||||
|
|
||||||
@@ -456,10 +457,12 @@ int main () {
|
|||||||
for (int i = 3; i >= 0; i --) printf("%X", (unsigned int)((rng_seed >> (8 * i)) & 255));
|
for (int i = 3; i >= 0; i --) printf("%X", (unsigned int)((rng_seed >> (8 * i)) & 255));
|
||||||
printf("\n\n");
|
printf("\n\n");
|
||||||
|
|
||||||
for (int i = 0; i < sizeof(block_changes) / sizeof(BlockChange); i ++) {
|
for (int i = 0; i < MAX_BLOCK_CHANGES; i ++) {
|
||||||
block_changes[i].block = 0xFF;
|
block_changes[i].block = 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (initSerializer()) exit(EXIT_FAILURE);
|
||||||
|
|
||||||
int server_fd, opt = 1;
|
int server_fd, opt = 1;
|
||||||
struct sockaddr_in server_addr, client_addr;
|
struct sockaddr_in server_addr, client_addr;
|
||||||
socklen_t addr_len = sizeof(client_addr);
|
socklen_t addr_len = sizeof(client_addr);
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
#include "registries.h"
|
#include "registries.h"
|
||||||
#include "worldgen.h"
|
#include "worldgen.h"
|
||||||
#include "structures.h"
|
#include "structures.h"
|
||||||
|
#include "serialize.h"
|
||||||
#include "procedures.h"
|
#include "procedures.h"
|
||||||
|
|
||||||
int client_states[MAX_PLAYERS * 2];
|
int client_states[MAX_PLAYERS * 2];
|
||||||
@@ -365,7 +366,7 @@ void makeBlockChange (short x, uint8_t y, short z, uint8_t block) {
|
|||||||
#endif
|
#endif
|
||||||
if (is_base_block) block_changes[i].block = 0xFF;
|
if (is_base_block) block_changes[i].block = 0xFF;
|
||||||
else block_changes[i].block = block;
|
else block_changes[i].block = block;
|
||||||
return;
|
return writeBlockChangesToDisk(i, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,6 +402,8 @@ void makeBlockChange (short x, uint8_t y, short z, uint8_t block) {
|
|||||||
if (i >= block_changes_count) {
|
if (i >= block_changes_count) {
|
||||||
block_changes_count = i + 1;
|
block_changes_count = i + 1;
|
||||||
}
|
}
|
||||||
|
// Write changes to disk (if applicable)
|
||||||
|
writeBlockChangesToDisk(last_real_entry + 1, last_real_entry + 15);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -412,6 +415,8 @@ void makeBlockChange (short x, uint8_t y, short z, uint8_t block) {
|
|||||||
block_changes[first_gap].y = y;
|
block_changes[first_gap].y = y;
|
||||||
block_changes[first_gap].z = z;
|
block_changes[first_gap].z = z;
|
||||||
block_changes[first_gap].block = block;
|
block_changes[first_gap].block = block;
|
||||||
|
// Write change to disk (if applicable)
|
||||||
|
writeBlockChangesToDisk(first_gap, first_gap);
|
||||||
// Extend future search range if we've appended to the end
|
// Extend future search range if we've appended to the end
|
||||||
if (first_gap == block_changes_count) {
|
if (first_gap == block_changes_count) {
|
||||||
block_changes_count ++;
|
block_changes_count ++;
|
||||||
@@ -1145,6 +1150,9 @@ void handleServerTick (int64_t time_since_last_tick) {
|
|||||||
sc_setHealth(player_data[i].client_fd, player_data[i].health, player_data[i].hunger, player_data[i].saturation);
|
sc_setHealth(player_data[i].client_fd, player_data[i].health, player_data[i].hunger, player_data[i].saturation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write player data to file (if applicable)
|
||||||
|
writePlayerDataToDisk();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the RNG seed ever hits 0, it'll never generate anything
|
* If the RNG seed ever hits 0, it'll never generate anything
|
||||||
* else. This is because the fast_rand function uses a simple
|
* else. This is because the fast_rand function uses a simple
|
||||||
@@ -1316,5 +1324,7 @@ void broadcastChestUpdate (int origin_fd, uint8_t *storage_ptr, uint16_t item, u
|
|||||||
sc_setContainerSlot(player_data[i].client_fd, 2, slot, count, item);
|
sc_setContainerSlot(player_data[i].client_fd, 2, slot, count, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writeChestChangesToDisk(storage_ptr, slot);
|
||||||
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
169
src/serialize.c
Normal file
169
src/serialize.c
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#include "globals.h"
|
||||||
|
|
||||||
|
#if defined(SYNC_WORLD_TO_DISK) && !defined(ESP_PLATFORM)
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "registries.h"
|
||||||
|
#include "serialize.h"
|
||||||
|
|
||||||
|
// Restores world data from disk, or writes world file if it doesn't exist
|
||||||
|
int initSerializer () {
|
||||||
|
|
||||||
|
// Attempt to open existing world file
|
||||||
|
FILE *file = fopen("world.bin", "rb");
|
||||||
|
if (file) {
|
||||||
|
|
||||||
|
// Read block changes from the start of the file directly into memory
|
||||||
|
size_t read = fread(block_changes, 1, sizeof(block_changes), file);
|
||||||
|
if (read != sizeof(block_changes)) {
|
||||||
|
printf("Read %u bytes from \"world.bin\", expected %u (block changes). Aborting.\n", read, sizeof(block_changes));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// Find the index of the last occupied entry to recover block_changes_count
|
||||||
|
for (int i = 0; i < MAX_BLOCK_CHANGES; i ++) {
|
||||||
|
if (block_changes[i].block == 0xFF) continue;
|
||||||
|
if (block_changes[i].block == B_chest) i += 14;
|
||||||
|
if (i >= block_changes_count) block_changes_count = i + 1;
|
||||||
|
}
|
||||||
|
// Seek past block changes to start reading player data
|
||||||
|
if (fseek(file, sizeof(block_changes), SEEK_SET) != 0) {
|
||||||
|
perror("Failed to seek to player data in \"world.bin\". Aborting.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// Read player data directly into memory
|
||||||
|
read = fread(player_data, 1, sizeof(player_data), file);
|
||||||
|
fclose(file);
|
||||||
|
if (read != sizeof(player_data)) {
|
||||||
|
printf("Read %u bytes from \"world.bin\", expected %u (player data). Aborting.\n", read, sizeof(player_data));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // World file doesn't exist or failed to open
|
||||||
|
printf("No \"world.bin\" file found, creating one...\n\n");
|
||||||
|
|
||||||
|
// Try to create the file in binary write mode
|
||||||
|
file = fopen("world.bin", "wb");
|
||||||
|
if (!file) {
|
||||||
|
perror(
|
||||||
|
"Failed to open \"world.bin\" for writing.\n"
|
||||||
|
"Consider checking permissions or disabling SYNC_WORLD_TO_DISK in \"globals.h\"."
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// Write initial block changes array
|
||||||
|
// This should be done after all entries have had `block` set to 0xFF
|
||||||
|
size_t written = fwrite(block_changes, 1, sizeof(block_changes), file);
|
||||||
|
if (written != sizeof(block_changes)) {
|
||||||
|
perror(
|
||||||
|
"Failed to write initial block data to \"world.bin\".\n"
|
||||||
|
"Consider checking permissions or disabling SYNC_WORLD_TO_DISK in \"globals.h\"."
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// Seek past written block changes to start writing player data
|
||||||
|
if (fseek(file, sizeof(block_changes), SEEK_SET) != 0) {
|
||||||
|
perror(
|
||||||
|
"Failed to seek past block changes in \"world.bin\"."
|
||||||
|
"Consider checking permissions or disabling SYNC_WORLD_TO_DISK in \"globals.h\"."
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// Write initial player data to disk (should be just nulls?)
|
||||||
|
written = fwrite(player_data, 1, sizeof(player_data), file);
|
||||||
|
fclose(file);
|
||||||
|
if (written != sizeof(player_data)) {
|
||||||
|
perror(
|
||||||
|
"Failed to write initial player data to \"world.bin\".\n"
|
||||||
|
"Consider checking permissions or disabling SYNC_WORLD_TO_DISK in \"globals.h\"."
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes a range of block change entries to disk
|
||||||
|
void writeBlockChangesToDisk (int from, int to) {
|
||||||
|
|
||||||
|
// Try to open the file in rw (without overwriting)
|
||||||
|
FILE *file = fopen("world.bin", "r+b");
|
||||||
|
if (!file) {
|
||||||
|
perror("Failed to open \"world.bin\". Block updates have been dropped.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = from; i <= to; i ++) {
|
||||||
|
// Seek to relevant offset in file
|
||||||
|
if (fseek(file, i * sizeof(BlockChange), SEEK_SET) != 0) {
|
||||||
|
fclose(file);
|
||||||
|
perror("Failed to seek in \"world.bin\". Block updates have been dropped.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Write block change entry to file
|
||||||
|
if (fwrite(&block_changes[i], 1, sizeof(BlockChange), file) != sizeof(BlockChange)) {
|
||||||
|
fclose(file);
|
||||||
|
perror("Failed to write to \"world.bin\". Block updates have been dropped.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes all player data to disk
|
||||||
|
void writePlayerDataToDisk () {
|
||||||
|
|
||||||
|
// Try to open the file in rw (without overwriting)
|
||||||
|
FILE *file = fopen("world.bin", "r+b");
|
||||||
|
if (!file) {
|
||||||
|
perror("Failed to open \"world.bin\". Player updates have been dropped.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Seek past block changes in file
|
||||||
|
if (fseek(file, sizeof(block_changes), SEEK_SET) != 0) {
|
||||||
|
fclose(file);
|
||||||
|
perror("Failed to seek in \"world.bin\". Player updates have been dropped.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Write full player data array to file
|
||||||
|
// Since this is a bigger write, it should ideally be done infrequently
|
||||||
|
if (fwrite(&player_data, 1, sizeof(player_data), file) != sizeof(player_data)) {
|
||||||
|
fclose(file);
|
||||||
|
perror("Failed to write to \"world.bin\". Player updates have been dropped.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ALLOW_CHESTS
|
||||||
|
// Writes a chest slot change to disk
|
||||||
|
void writeChestChangesToDisk (uint8_t *storage_ptr, uint8_t slot) {
|
||||||
|
/**
|
||||||
|
* More chest-related memory hacks!!
|
||||||
|
*
|
||||||
|
* Since chests are implemented in the block_changes array, any
|
||||||
|
* changes to the contents of a chest have to be synced to the block
|
||||||
|
* changes part of the world file. The index of the "blocks" is
|
||||||
|
* determined as such:
|
||||||
|
*
|
||||||
|
* The storage pointer points to the block entry directly following
|
||||||
|
* the chest itself. To get the index of this entry, we can subtract
|
||||||
|
* the pointer to the block changes array (cast to uint8_t*) from the
|
||||||
|
* storage pointer. This gets us the amount of bytes between the start
|
||||||
|
* of the block changes array and the chest's item data, as a pointer.
|
||||||
|
* To get the actual block index, we cast this weird pointer to an
|
||||||
|
* integer, and divide it by the byte size of the BlockChange struct.
|
||||||
|
* Finally, the chest slot divided by 2 is added to this index to get
|
||||||
|
* the block entry pertaining to the relevant chest slot, as each
|
||||||
|
* entry encodes exactly 2 slots.
|
||||||
|
*/
|
||||||
|
int index = (int)(storage_ptr - (uint8_t *)block_changes) / sizeof(BlockChange) + slot / 2;
|
||||||
|
writeBlockChangesToDisk(index, index);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
Reference in New Issue
Block a user