Thing > entity, also try out anton's pre-alloced with generations thing
This commit is contained in:
@@ -40,6 +40,12 @@ fn void* arenaAlloc(Arena* arena, u64 size) {
|
||||
return memory;
|
||||
}
|
||||
|
||||
fn void* arenaAllocZero(Arena* a, u64 size) {
|
||||
void* result = arenaAlloc(a, size);
|
||||
MemoryZero(result, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn void* arenaAllocArraySized(Arena* arena, u64 elem_size, u64 count) {
|
||||
return arenaAlloc(arena, elem_size * count);
|
||||
}
|
||||
|
||||
+34
-22
@@ -49,21 +49,21 @@ typedef enum SpeechTailDirection {
|
||||
SpeechTailDirection_Count
|
||||
} SpeechTailDirection;
|
||||
|
||||
typedef struct Entity {
|
||||
EntityType type;
|
||||
typedef struct Thing {
|
||||
ThingType type;
|
||||
u8 x;
|
||||
u8 y;
|
||||
u8 color;
|
||||
u64 features;
|
||||
u64 id;
|
||||
StringChunkList name;
|
||||
} Entity;
|
||||
} Thing;
|
||||
|
||||
typedef struct EntityList {
|
||||
typedef struct ThingList {
|
||||
u64 length; // the currently used length
|
||||
u64 capacity;
|
||||
Entity* items;
|
||||
} EntityList;
|
||||
Thing* items;
|
||||
} ThingList;
|
||||
|
||||
typedef struct ParsedServerMessage {
|
||||
Message type;
|
||||
@@ -74,7 +74,7 @@ typedef struct ParsedServerMessage {
|
||||
u64 id;
|
||||
u64 server_frame;
|
||||
XYZ xyz;
|
||||
//Entity entities[PARSED_CLIENT_ENTITY_LEN];
|
||||
//Thing things[PARSED_CLIENT_THING_LEN];
|
||||
//u64 ids[PARSED_IDS_LEN];
|
||||
} ParsedServerMessage;
|
||||
|
||||
@@ -105,11 +105,11 @@ typedef struct MenuState {
|
||||
typedef struct GameState {
|
||||
Screen screen;
|
||||
Screen old_screen;
|
||||
EntityList entities;
|
||||
Entity me;
|
||||
ThingList things;
|
||||
Thing me;
|
||||
u64 server_frame;
|
||||
u64 loop_count;
|
||||
Arena entity_arena;
|
||||
Arena thing_arena;
|
||||
StringArena string_arena;
|
||||
LoginState login_state;
|
||||
MenuState menu;
|
||||
@@ -175,16 +175,16 @@ fn ParsedServerMessage* psmThreadSafeNonblockingQueuePop(ParsedServerMessageThre
|
||||
return result;
|
||||
}
|
||||
|
||||
fn void entityPush(EntityList* list, Entity e) {
|
||||
fn void thingPush(ThingList* list, Thing e) {
|
||||
if (list->length >= list->capacity) {
|
||||
arenaAllocArray(&state.entity_arena, Entity, list->capacity);
|
||||
arenaAllocArray(&state.thing_arena, Thing, list->capacity);
|
||||
list->capacity = list->capacity * 2;
|
||||
}
|
||||
list->items[list->length] = e;
|
||||
list->length += 1;
|
||||
}
|
||||
|
||||
fn bool entityDelete(EntityList* list, u64 id) {
|
||||
fn bool thingDelete(ThingList* list, u64 id) {
|
||||
assert(list->length > 0);
|
||||
if (list->items[list->length - 1].id == id) {
|
||||
list->length -= 1;
|
||||
@@ -342,10 +342,10 @@ fn void renderStaticAssetToPixelBuffer(TuiState* tui, u8* asset, u32 len, u16 x,
|
||||
fn void clearServerSentState() {
|
||||
//memset(&state.current_room, 0, sizeof(RenderableRoom));
|
||||
|
||||
arenaClear(&state.entity_arena);
|
||||
state.entities.capacity = 64;
|
||||
state.entities.length = 0;
|
||||
state.entities.items = arenaAllocArray(&state.entity_arena, Entity, state.entities.capacity);
|
||||
arenaClear(&state.thing_arena);
|
||||
state.things.capacity = 64;
|
||||
state.things.length = 0;
|
||||
state.things.items = arenaAllocArray(&state.thing_arena, Thing, state.things.capacity);
|
||||
}
|
||||
|
||||
fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 socket) {
|
||||
@@ -692,10 +692,10 @@ i32 main(i32 argc, ptr argv[]) {
|
||||
|
||||
// clear and init the state
|
||||
arenaInit(&permanent_arena);
|
||||
arenaInit(&state.entity_arena);
|
||||
state.entities.capacity = 64;
|
||||
state.entities.length = 0;
|
||||
state.entities.items = arenaAllocArray(&state.entity_arena, Entity, state.entities.capacity);
|
||||
arenaInit(&state.thing_arena);
|
||||
state.things.capacity = 64;
|
||||
state.things.length = 0;
|
||||
state.things.items = arenaAllocArray(&state.thing_arena, Thing, state.things.capacity);
|
||||
arenaInit(&state.string_arena.a);
|
||||
state.string_arena.mutex = newMutex();
|
||||
state.message_input = stringChunkListInit(&state.string_arena);
|
||||
@@ -721,7 +721,19 @@ i32 main(i32 argc, ptr argv[]) {
|
||||
printf("bad network startup");
|
||||
exit(1);
|
||||
}
|
||||
state.client = createUDPClient(7777, argc > 1 ? argv[1] : NULL);
|
||||
ptr server_address = NULL;
|
||||
if (argc > 1) {
|
||||
server_address = argv[1];
|
||||
} else {
|
||||
printf("Server IP Address: ");
|
||||
char input_string[16] = { 0 };
|
||||
scanf("%15s", input_string);
|
||||
printf("%s", input_string);
|
||||
if (input_string[0] && input_string[1] && input_string[2] && input_string[3]) {
|
||||
server_address = input_string;
|
||||
}
|
||||
}
|
||||
state.client = createUDPClient(7777, server_address);
|
||||
|
||||
// "hardcoded" keep alive message to periodically send to server
|
||||
state.keep_alive_msg.address = state.client.server_address;
|
||||
|
||||
+12
-12
@@ -4,17 +4,17 @@
|
||||
#define MAX_SCREEN_HEIGHT 300
|
||||
#define MAX_SCREEN_WIDTH 800
|
||||
|
||||
fn str charForEntity(EntityType e) {
|
||||
fn str charForThing(ThingType e) {
|
||||
switch (e) {
|
||||
case EntityWall:
|
||||
case ThingWall:
|
||||
return "# ";
|
||||
case EntityDoor:
|
||||
case ThingDoor:
|
||||
return "🚪";
|
||||
case EntityCharacter:
|
||||
case ThingCharacter:
|
||||
return "🧙";
|
||||
//return "웃";
|
||||
case EntityNull:
|
||||
case EntityType_Count:
|
||||
case ThingNull:
|
||||
case ThingType_Count:
|
||||
//default: // commented out so that we get a warning for missing entity
|
||||
return " ";
|
||||
}
|
||||
@@ -38,21 +38,21 @@ fn void renderRoom(Pixel* buf, u32 x, u32 y, RenderableRoom* room, Dim2 screen_d
|
||||
}
|
||||
u32 bufpos = (x+1+(i*2)) + (screen_dimensions.width*(y+1+j));
|
||||
|
||||
str fg_char = charForEntity(room->foreground[roompos]);
|
||||
str fg_char = charForThing(room->foreground[roompos]);
|
||||
RGB background_color = colorForTile(room->background[roompos]);
|
||||
if (room->visible[roompos]) {
|
||||
buf[bufpos].foreground = ansiColorForEntity(room->foreground[roompos]);
|
||||
buf[bufpos].foreground = ansiColorForThing(room->foreground[roompos]);
|
||||
if (room->light[roompos] < VISIBLE_BRIGHTNESS_CUTOFF) {
|
||||
fg_char = charForEntity(EntityMurkyUnknown);
|
||||
fg_char = charForThing(ThingMurkyUnknown);
|
||||
background_color = rgbDarken(background_color, 0.8);
|
||||
}
|
||||
} else if (room->memory[roompos] == RememberedTileQualityDim) {
|
||||
background_color = rgbDarken(background_color, 0.4);
|
||||
buf[bufpos].foreground = ansiColorForEntity(room->foreground[roompos]);
|
||||
fg_char = charForEntity(EntityMurkyUnknown);
|
||||
buf[bufpos].foreground = ansiColorForThing(room->foreground[roompos]);
|
||||
fg_char = charForThing(ThingMurkyUnknown);
|
||||
} else if (room->memory[roompos] == RememberedTileQualityClear) {
|
||||
background_color = rgbDarken(background_color, 0.6);
|
||||
buf[bufpos].foreground = ansiColorForEntity(room->foreground[roompos]);
|
||||
buf[bufpos].foreground = ansiColorForThing(room->foreground[roompos]);
|
||||
}
|
||||
buf[bufpos].background = rgbToAnsi(background_color);
|
||||
Utf8Character first_character_class = classifyUtf8Character((u8)fg_char[0]);
|
||||
|
||||
+132
-164
@@ -17,7 +17,7 @@
|
||||
#include "string_chunk.c"
|
||||
|
||||
///// CONSTANTS
|
||||
#define MAX_ENTITIES (2<<18)
|
||||
#define MAX_THINGS (2<<18)
|
||||
#define LEFT_ROOM_ENTITES_LEN (KB(1))
|
||||
#define ROOM_MAP_COLLISIONS_LEN MAX_ROOMS/8
|
||||
#define CLIENT_COMMAND_LIST_LEN 8
|
||||
@@ -57,22 +57,34 @@ typedef struct ParsedClientCommandThreadQueue {
|
||||
Cond not_full;
|
||||
} ParsedClientCommandThreadQueue;
|
||||
|
||||
typedef struct Entity {
|
||||
typedef struct ThingRef {
|
||||
u32 i;
|
||||
u32 gen;
|
||||
} ThingRef;
|
||||
|
||||
typedef struct Thing {
|
||||
ThingType type;
|
||||
bool changed;
|
||||
u8 x;
|
||||
u8 y;
|
||||
u8 color;
|
||||
EntityType type;
|
||||
u32 misc;
|
||||
u64 id;
|
||||
ThingRef id;
|
||||
u64 features;
|
||||
} Entity;
|
||||
} Thing;
|
||||
|
||||
typedef struct Things {
|
||||
u32 first_free;
|
||||
u32 next_free[MAX_THINGS];
|
||||
u32 gen[MAX_THINGS];
|
||||
Thing things[MAX_THINGS];
|
||||
} Things;
|
||||
|
||||
typedef struct Account {
|
||||
u64 id;
|
||||
String name;
|
||||
String pw;
|
||||
u64 eid;
|
||||
ThingRef eid;
|
||||
} Account;
|
||||
|
||||
typedef struct AccountChunk {
|
||||
@@ -82,30 +94,10 @@ typedef struct AccountChunk {
|
||||
Account* items; // the actual accounts
|
||||
} AccountChunk;
|
||||
|
||||
typedef struct EntityList {
|
||||
u64 length; // the currently used length
|
||||
u64 capacity;
|
||||
Entity* items;
|
||||
} EntityList;
|
||||
|
||||
typedef struct EntityChunk {
|
||||
u64 length; // the currently used # of entities in this chunk
|
||||
u64 capacity; // the "chunk size" / space in this chunk
|
||||
struct EntityChunk* next; // the next chunk
|
||||
Entity* items; // the actual entities
|
||||
} EntityChunk;
|
||||
|
||||
typedef struct ChunkedEntityList {
|
||||
u64 length; // the current number of entities
|
||||
u64 chunk_size; // the # of entities per chunk
|
||||
u64 chunks; // the # of chunks in this list so far
|
||||
EntityChunk* first; // the first chunk of entities
|
||||
} ChunkedEntityList;
|
||||
|
||||
typedef struct Client {
|
||||
u16 lan_port;
|
||||
i32 lan_ip;
|
||||
u64 character_eid;
|
||||
ThingRef character_eid;
|
||||
u64 account_id;
|
||||
SocketAddress address;
|
||||
CommandType commands[CLIENT_COMMAND_LIST_LEN];
|
||||
@@ -122,23 +114,85 @@ typedef struct State {
|
||||
Mutex client_mutex;
|
||||
Mutex mutex;
|
||||
ClientList clients;
|
||||
u64 next_eid;
|
||||
AccountChunk accounts;
|
||||
u64 frame;
|
||||
Arena game_scratch;
|
||||
StringArena string_arena;
|
||||
ParsedClientCommandThreadQueue* network_recv_queue;
|
||||
OutgoingMessageQueue* network_send_queue;
|
||||
Things* things;
|
||||
} State;
|
||||
|
||||
///// Global Variables
|
||||
global State state = { 0 };
|
||||
global Arena permanent_arena = { 0 };
|
||||
global const Entity NULL_ENTITY = { 0 };
|
||||
global const Thing NULL_THING = { 0 };
|
||||
global bool debug_mode = false;
|
||||
global ChunkedEntityList free_chunks = { 0, CHUNK_SIZE, 0, NULL };
|
||||
|
||||
///// functionImplementations()
|
||||
fn bool thingRefsEq(ThingRef a, ThingRef b) {
|
||||
return a.i == b.i && a.gen == b.gen;
|
||||
}
|
||||
|
||||
fn void thingsInit(Things* things) {
|
||||
MemoryZero(things->next_free, sizeof(things->next_free));
|
||||
MemoryZero(things->gen, sizeof(things->gen));
|
||||
MemoryZero(things->things, sizeof(things->things));
|
||||
// 0 is the NULL_THING
|
||||
things->first_free = 1;
|
||||
// they are all free initially,
|
||||
// except the last slot has nothing to point to, and
|
||||
// the first slot is for the NULL_THING
|
||||
for (u32 i = 1; i < MAX_THINGS-1; i++) {
|
||||
things->next_free[i] = i+1;
|
||||
}
|
||||
}
|
||||
|
||||
fn u32 thingDeref(Things* t, ThingRef ref) {
|
||||
if (ref.i > 0 && ref.i < MAX_THINGS && t->gen[ref.i] == ref.gen) {
|
||||
return ref.i;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn ThingRef thingAdd(Things* t, Thing thing) {
|
||||
ThingRef result = {0};
|
||||
u32 slot = t->first_free;
|
||||
if (slot) {
|
||||
MemoryCopyStruct(&t->things[slot], &thing);
|
||||
t->gen[slot] += 1;
|
||||
t->first_free = t->next_free[slot];
|
||||
t->next_free[slot] = 0; // sort of indicates that this slot is used
|
||||
result.gen = t->gen[slot];
|
||||
result.i = slot;
|
||||
t->things[slot].id = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
fn Thing* thingGet(Things* t, ThingRef ref) {
|
||||
u32 slot = thingDeref(t, ref);
|
||||
if (slot) {
|
||||
return &t->things[slot];
|
||||
} else {
|
||||
return (Thing*)&NULL_THING;
|
||||
}
|
||||
}
|
||||
|
||||
fn bool thingRemove(Things* t, ThingRef ref) {
|
||||
u32 slot = thingDeref(t, ref);
|
||||
if (slot) {
|
||||
if (t->first_free) {
|
||||
t->next_free[slot] = t->first_free;
|
||||
}
|
||||
t->first_free = slot;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
fn ParsedClientCommandThreadQueue* newPCCThreadQueue(Arena* a) {
|
||||
ParsedClientCommandThreadQueue* result = arenaAlloc(a, sizeof(ParsedClientCommandThreadQueue));
|
||||
MemoryZero(result, (sizeof *result));
|
||||
@@ -182,13 +236,14 @@ fn ParsedClientCommand* pccThreadSafeNonblockingQueuePop(ParsedClientCommandThre
|
||||
return result;
|
||||
}
|
||||
|
||||
fn u64 entitySerialize(Entity current, Account* acct, u64 index, u8 bytes[]) {
|
||||
// send entity header (common to all entity types)
|
||||
index += writeU64ToBufferLE(bytes + index, current.id);
|
||||
fn u64 thingSerialize(Thing current, Account* acct, u64 index, u8 bytes[]) {
|
||||
// send thing header (common to all thing types)
|
||||
index += writeU32ToBufferLE(bytes + index, current.id.i);
|
||||
index += writeU32ToBufferLE(bytes + index, current.id.gen);
|
||||
index += writeU64ToBufferLE(bytes + index, current.features);
|
||||
bytes[index++] = current.x;
|
||||
bytes[index++] = current.y;
|
||||
bytes[index++] = (u8)current.type; // EntityType
|
||||
bytes[index++] = (u8)current.type; // ThingType
|
||||
|
||||
//if (CheckFlag(current.features, FeatureRegensHp)) {
|
||||
// index += writeU16ToBufferLE(bytes + index, current.hp);
|
||||
@@ -196,7 +251,7 @@ fn u64 entitySerialize(Entity current, Account* acct, u64 index, u8 bytes[]) {
|
||||
//}
|
||||
|
||||
// send character-specific details (color and name)
|
||||
if (current.type == EntityCharacter) {
|
||||
if (current.type == ThingCharacter) {
|
||||
bytes[index++] = current.color;
|
||||
// send name string
|
||||
index += writeU64ToBufferLE(bytes + index, acct->name.length);
|
||||
@@ -207,10 +262,10 @@ fn u64 entitySerialize(Entity current, Account* acct, u64 index, u8 bytes[]) {
|
||||
return index;
|
||||
}
|
||||
|
||||
fn u64 entityFeaturesFromType(EntityType type) {
|
||||
fn u64 thingFeaturesFromType(ThingType type) {
|
||||
u64 result = 0;
|
||||
switch (type) {
|
||||
case EntityCharacter: {
|
||||
case ThingCharacter: {
|
||||
SetFlag(result, FeatureWalksAround);
|
||||
SetFlag(result, FeatureCanFight);
|
||||
} break;
|
||||
@@ -233,11 +288,11 @@ fn Account* findAccountById(u64 id) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fn Account* findAccountByEId(u64 id) {
|
||||
fn Account* findAccountByEId(ThingRef id) {
|
||||
AccountChunk* current = &state.accounts;
|
||||
while (current != NULL) {
|
||||
for (u32 i = 0; i < current->length; i++) {
|
||||
if (current->items[i].eid == id) {
|
||||
if (thingRefsEq(current->items[i].eid, id)) {
|
||||
return ¤t->items[i];
|
||||
}
|
||||
}
|
||||
@@ -285,93 +340,6 @@ fn Account* newAccount(Arena* a, Account details) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fn u64 pushEntity(EntityChunk* chunk, Entity e) {
|
||||
assert(chunk->length < chunk->capacity);
|
||||
chunk->items[chunk->length] = e;
|
||||
u64 result = chunk->length;
|
||||
chunk->length += 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
fn bool isSpaceInChunk(EntityChunk* chunk) {
|
||||
return chunk->length < chunk->capacity;
|
||||
}
|
||||
|
||||
fn bool allChunksFull(ChunkedEntityList list) {
|
||||
return list.length >= (list.chunk_size * list.chunks);
|
||||
}
|
||||
|
||||
fn EntityChunk* pushNewChunk(Arena* a, ChunkedEntityList* list) {
|
||||
EntityChunk* new_chunk;
|
||||
if (free_chunks.length > 0) {
|
||||
new_chunk = free_chunks.first;
|
||||
free_chunks.length -= 1;
|
||||
free_chunks.first = new_chunk->next;
|
||||
} else {
|
||||
new_chunk = arenaAlloc(a, sizeof(EntityChunk));
|
||||
// alloc the new chunk of entities
|
||||
new_chunk->length = 0;
|
||||
new_chunk->capacity = list->chunk_size;
|
||||
new_chunk->next = NULL;
|
||||
new_chunk->items = arenaAllocArray(a, Entity, list->chunk_size);
|
||||
}
|
||||
// bookeeping in the list
|
||||
list->chunks += 1;
|
||||
if (list->first == NULL) {
|
||||
list->first = new_chunk;
|
||||
} else {
|
||||
EntityChunk* last = list->first;
|
||||
while (last->next != NULL) {
|
||||
last = last->next;
|
||||
}
|
||||
last->next = new_chunk;
|
||||
}
|
||||
return new_chunk;
|
||||
}
|
||||
|
||||
fn Entity* entityPtrFromChunkList(ChunkedEntityList* list, i32 index) {
|
||||
Entity* result = (Entity*)&NULL_ENTITY;
|
||||
i32 chunk_index = index / list->chunk_size;
|
||||
EntityChunk* chunk = list->first;
|
||||
for (i32 i = 0; i < chunk_index; i++) {
|
||||
chunk = chunk->next;
|
||||
}
|
||||
result = &chunk->items[index % list->chunk_size];
|
||||
return result;
|
||||
}
|
||||
|
||||
fn Entity entityFromChunkList(ChunkedEntityList* list, i32 index) {
|
||||
Entity result = {0};
|
||||
i32 chunk_index = index / list->chunk_size;
|
||||
EntityChunk* chunk = list->first;
|
||||
for (i32 i = 0; i < chunk_index; i++) {
|
||||
chunk = chunk->next;
|
||||
}
|
||||
result = chunk->items[index % list->chunk_size];
|
||||
return result;
|
||||
}
|
||||
|
||||
fn bool deleteLastEntity(ChunkedEntityList* list, EntityChunk* last_chunk, EntityChunk* second_to_last_chunk) {
|
||||
last_chunk->length -= 1;
|
||||
list->length -= 1;
|
||||
// don't delete the room's only chunk, but otherwise move the chunk to the free-list
|
||||
if (last_chunk->length == 0 && last_chunk != list->first) {
|
||||
list->chunks -= 1;
|
||||
second_to_last_chunk->next = NULL;
|
||||
free_chunks.length += 1;
|
||||
EntityChunk* last_free_chunk = free_chunks.first;
|
||||
if (last_free_chunk) {
|
||||
while (last_free_chunk->next != NULL) {
|
||||
last_free_chunk = last_free_chunk->next;
|
||||
}
|
||||
last_free_chunk->next = last_chunk;
|
||||
} else {
|
||||
free_chunks.first = last_chunk;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn void exitWithErrorMessage(ptr msg) {
|
||||
printf("error: %s", msg);
|
||||
exit(1);
|
||||
@@ -384,7 +352,7 @@ fn u32 pushClient(ClientList* clients, SocketAddress addr) {
|
||||
|
||||
// first, try to overwrite an old dc'ed client
|
||||
for (u32 i = 1; i < clients->length; i++) {
|
||||
if (clients->items[i].character_eid == 0) {
|
||||
if (clients->items[i].account_id == 0) {
|
||||
clients->items[i] = new_client;
|
||||
return i;
|
||||
}
|
||||
@@ -398,12 +366,12 @@ fn u32 pushClient(ClientList* clients, SocketAddress addr) {
|
||||
return result;
|
||||
}
|
||||
|
||||
fn bool deleteClientByEId(ClientList* clients, u64 id) {
|
||||
fn bool deleteClientByEId(ClientList* clients, ThingRef id) {
|
||||
bool succeeded = false;
|
||||
Client blank_client = {0};
|
||||
// i=1 because first client is null-client
|
||||
for (u32 i = 1; i < clients->length && !succeeded; i++) {
|
||||
if (clients->items[i].character_eid == id) {
|
||||
if (thingRefsEq(clients->items[i].character_eid, id)) {
|
||||
clients->items[i] = blank_client;
|
||||
succeeded = true;
|
||||
}
|
||||
@@ -411,10 +379,10 @@ fn bool deleteClientByEId(ClientList* clients, u64 id) {
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
fn u32 findClientHandleByEId(ClientList* clients, u64 id) {
|
||||
fn u32 findClientHandleByEId(ClientList* clients, ThingRef id) {
|
||||
for (u32 i = 0; i < clients->length; i++) {
|
||||
Client c = clients->items[i];
|
||||
if (c.character_eid == id) {
|
||||
if (thingRefsEq(c.character_eid, id)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -533,7 +501,7 @@ fn void* sendNetworkUpdates(void* sock) {
|
||||
memset(&state.clients.items[i], 0, sizeof(Client));
|
||||
continue;
|
||||
}
|
||||
if (client.character_eid == 0) {
|
||||
if (client.character_eid.i == 0) {
|
||||
continue; // they are still creating their character
|
||||
}
|
||||
|
||||
@@ -549,6 +517,18 @@ fn void* sendNetworkUpdates(void* sock) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fn bool messageSendCharacterId(SocketAddress sender, ThingRef id) {
|
||||
UDPMessage outgoing_message = {0};
|
||||
outgoing_message.address = sender;
|
||||
outgoing_message.bytes_len = 9;
|
||||
outgoing_message.bytes[0] = (u8)MessageCharacterId;
|
||||
writeU32ToBufferLE(outgoing_message.bytes + 1, id.i);
|
||||
writeU32ToBufferLE(outgoing_message.bytes + 5, id.gen);
|
||||
outgoingMessageQueuePush(state.network_send_queue, &outgoing_message);
|
||||
printf("MessageCharacterId sent\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
fn void* gameLoop(void* params) {
|
||||
LaneCtx* lane_ctx = (LaneCtx*)params;
|
||||
ThreadContext tctx = {
|
||||
@@ -639,16 +619,11 @@ fn void* gameLoop(void* params) {
|
||||
existing_account = newAccount(&permanent_arena, new_account);
|
||||
}
|
||||
client->account_id = existing_account->id;
|
||||
if (existing_account->eid != 0) {
|
||||
if (existing_account->eid.i != 0) {
|
||||
client->character_eid = existing_account->eid;
|
||||
|
||||
// tell the client their character id
|
||||
outgoing_message.bytes[0] = (u8)MessageCharacterId;
|
||||
writeU64ToBufferLE(outgoing_message.bytes + 1, existing_account->eid);
|
||||
outgoing_message.bytes_len = 9;
|
||||
outgoing_message.address = sender;
|
||||
outgoingMessageQueuePush(state.network_send_queue, &outgoing_message);
|
||||
printf("MessageCharacterId sent\n");
|
||||
messageSendCharacterId(sender, existing_account->eid);
|
||||
} else {
|
||||
// tell the client they made a new account
|
||||
outgoing_message.bytes[0] = (u8)MessageNewAccountCreated;
|
||||
@@ -657,32 +632,26 @@ fn void* gameLoop(void* params) {
|
||||
outgoingMessageQueuePush(state.network_send_queue, &outgoing_message);
|
||||
printf("MessageNewAccountCreated sent\n");
|
||||
}
|
||||
printf("eid=%lld, client_handle=%d, acct_id=%lld\n", existing_account->eid, client_handle, existing_account->id);
|
||||
printf("eid=%d, client_handle=%d, acct_id=%lld\n", existing_account->eid.i, client_handle, existing_account->id);
|
||||
} break;
|
||||
case CommandCreateCharacter: {
|
||||
if (client->character_eid == 0) {
|
||||
if (client->character_eid.i == 0) {
|
||||
// Create new character
|
||||
XYZ room_xyz = {0, 0, 0 };
|
||||
Entity character = {
|
||||
.type = EntityCharacter,
|
||||
.id = state.next_eid++,
|
||||
Thing character = {
|
||||
.type = ThingCharacter,
|
||||
.changed = true,
|
||||
.features = entityFeaturesFromType(EntityCharacter),
|
||||
.features = thingFeaturesFromType(ThingCharacter),
|
||||
.color = msg.byte,
|
||||
};
|
||||
dbg("made new character id=%ld\n", character.id);
|
||||
client->character_eid = character.id;
|
||||
Account* account = findAccountById(client->account_id);
|
||||
account->eid = character.id;
|
||||
printf("character_eid=%lld, client_handle=%d, acct_id=%lld\n", account->eid, client_handle, account->id);
|
||||
printf("character_eid=%d, client_handle=%d, acct_id=%lld\n", account->eid.i, client_handle, account->id);
|
||||
|
||||
// tell the client their character id
|
||||
outgoing_message.address = sender;
|
||||
outgoing_message.bytes_len = 9;
|
||||
outgoing_message.bytes[0] = (u8)MessageCharacterId;
|
||||
writeU64ToBufferLE(outgoing_message.bytes + 1, character.id);
|
||||
outgoingMessageQueuePush(state.network_send_queue, &outgoing_message);
|
||||
printf("MessageCharacterId sent\n");
|
||||
messageSendCharacterId(sender, character.id);
|
||||
} else {
|
||||
printf("client tried to create a character when he already has one.");
|
||||
}
|
||||
@@ -728,18 +697,14 @@ fn void* gameLoop(void* params) {
|
||||
// THE SERVER
|
||||
i32 main(i32 argc, ptr argv[]) {
|
||||
osInit();
|
||||
// multi-thread architecture:
|
||||
// architecture:
|
||||
// - the sendNetworkUpdates() infinite loop, which sends a UDP snapshot-or-delta update to each connected client N/sec
|
||||
// - the receiveNetworkUpdates() infinte loop, which waits for new UDP messages from clients
|
||||
// - the gameLoop() which just inifinite loops every "tick" and processes user input and updates gameworld state
|
||||
// - TODO: actually make this N "lanes" as described https://www.rfleury.com/p/multi-core-by-default
|
||||
// such that we can split room-work among all the various cores on our machine
|
||||
// - one is the sendNetworkUpdates() infinite loop, which sends a UDP snapshot-or-delta update to each connected client N/sec
|
||||
// - one is the receiveNetworkUpdates() infinte loop, which waits for new UDP messages from clients
|
||||
// - this is N "lanes" as described https://www.rfleury.com/p/multi-core-by-default
|
||||
// such that we can split room-work among all the various cores on our machine
|
||||
|
||||
// 1. initialize gameworld, and spin off infinite game-loop thread
|
||||
// 2. spin off sendNetworkUpdates() infinite loop thread
|
||||
// 3. infinitely wait for incoming UDP messages and process them (usually by just dropping user-commands into the relevant block of shared memory)
|
||||
|
||||
// 1. initialize gameworld, and spin off infinite game-loop thread
|
||||
// initialize gameworld, and spin off infinite game-loop thread
|
||||
arenaInit(&permanent_arena);
|
||||
arenaInit(&state.game_scratch);
|
||||
arenaInit(&state.string_arena.a);
|
||||
@@ -755,14 +720,14 @@ i32 main(i32 argc, ptr argv[]) {
|
||||
state.clients.items = (Client*)arenaAllocArray(&permanent_arena, Client, SERVER_MAX_CLIENTS);
|
||||
state.accounts.capacity = ACCOUNT_CHUNK_SIZE;
|
||||
state.accounts.items = arenaAllocArray(&permanent_arena, Account, ACCOUNT_CHUNK_SIZE);
|
||||
state.things = arenaAllocZero(&permanent_arena, sizeof(Things));
|
||||
|
||||
// 2. spin off sendNetworkUpdates() infinite loop thread
|
||||
// networking threads (send + receive)
|
||||
UDPServer listener = createUDPServer(SERVER_PORT);
|
||||
if (!listener.ready) {
|
||||
exitWithErrorMessage("Couldn't start the udp server");
|
||||
}
|
||||
Thread send_thread = spawnThread(&sendNetworkUpdates, &listener.server_socket);
|
||||
// 3. infinitely wait for incoming UDP messages and process them (usually by just dropping user-commands into the relevant block of shared memory)
|
||||
Thread recv_thread = spawnThread(&receiveNetworkUpdates, &listener);
|
||||
|
||||
u64 lane_broadcast_val = 0;
|
||||
@@ -780,6 +745,9 @@ i32 main(i32 argc, ptr argv[]) {
|
||||
osThreadJoin(game_threads[i], MAX_u64);
|
||||
}
|
||||
|
||||
osThreadJoin(send_thread, MAX_u64);
|
||||
osThreadJoin(recv_thread, MAX_u64);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+14
-13
@@ -6,21 +6,24 @@
|
||||
///// #define some game-tunable constants
|
||||
#define GAME_CONSTANT_ONE (1)
|
||||
#define GAME_CONSTANT_TWO (2)
|
||||
#define THING_HEADER_MESSAGE_SIZE (8+8+1+1+1)
|
||||
#define THING_MESSAGE_SIZE (THING_HEADER_MESSAGE_SIZE+2+2+2+2+8+1+1)
|
||||
|
||||
typedef enum EntityFeature {
|
||||
typedef enum ThingFeature {
|
||||
FeatureWalksAround,
|
||||
FeatureCanFight,
|
||||
EntityFeature_Count
|
||||
} EntityFeature;
|
||||
ThingFeature_Count
|
||||
} ThingFeature;
|
||||
|
||||
typedef enum EntityType {
|
||||
EntityNull,
|
||||
EntityWall,
|
||||
EntityDoor,
|
||||
EntityCharacter,
|
||||
EntityType_Count,
|
||||
} EntityType;
|
||||
static const char* ENTITY_STRINGS[] = {
|
||||
typedef enum ThingType {
|
||||
ThingNull,
|
||||
ThingWall,
|
||||
ThingDoor,
|
||||
ThingCharacter,
|
||||
ThingType_Count,
|
||||
} ThingType;
|
||||
|
||||
static const char* THING_TYPE_STRINGS[] = {
|
||||
"NULL",
|
||||
"Wall",
|
||||
"Door",
|
||||
@@ -58,8 +61,6 @@ static const char* command_type_strings[] = {
|
||||
"CreateCharacter",
|
||||
};
|
||||
|
||||
#define ENTITY_HEADER_MESSAGE_SIZE (8+8+1+1+1)
|
||||
#define ENTITY_MESSAGE_SIZE (ENTITY_HEADER_MESSAGE_SIZE+2+2+2+2+8+1+1)
|
||||
typedef enum Message {
|
||||
MessageInvalid,
|
||||
MessageCharacterId,
|
||||
|
||||
Reference in New Issue
Block a user