]> git.lizzy.rs Git - dragonblocks_alpha.git/blobdiff - src/server/server_player.c
refactoring
[dragonblocks_alpha.git] / src / server / server_player.c
index e48019615568ed9efbb2567b18ab801d10b66d7a..1f464f93e394a374246b9171d9a122980ecf4ed1 100644 (file)
+#include <dragonstd/map.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <pthread.h>
-#include <dragonstd/list.h>
+#include "day.h"
+#include "entity.h"
+#include "perlin.h"
 #include "server/database.h"
 #include "server/server_config.h"
-#include "server/server_map.h"
 #include "server/server_player.h"
-#include "perlin.h"
-#include "day.h"
-#include "util.h"
-
-static bool shutting_down = false;
-static pthread_rwlock_t shutting_down_lock;
+#include "server/server_terrain.h"
 
-static List players;
-static pthread_rwlock_t players_lock;
+static Map players;
+static Map players_named;
 
-static List names;
-static pthread_rwlock_t names_lock;
+// main thread
+// called on server shutdown
+static void player_drop(ServerPlayer *player)
+{
+       pthread_rwlock_rdlock(&player->lock_peer);
+       pthread_t recv_thread = player->peer ? player->peer->recv_thread : 0;
+       pthread_rwlock_unlock(&player->lock_peer);
 
-static u64 next_id = 1;
+       server_player_disconnect(player);
+       if (recv_thread)
+               pthread_join(recv_thread, NULL);
 
-static bool list_compare_u64(u64 *p1, u64 *p2)
-{
-       return *p1 == *p2;
+       refcount_drp(&player->rc); // map no longer has a reference to player
 }
 
-static bool get_lock(pthread_rwlock_t *lock, bool write)
+// any thread
+// called when all refs have been dropped
+static void player_delete(ServerPlayer *player)
 {
-       pthread_rwlock_rdlock(&shutting_down_lock);
-       if (shutting_down) {
-               pthread_rwlock_unlock(&shutting_down_lock);
-               return false;
-       }
+       refcount_dst(&player->rc);
 
-       if (write)
-               pthread_rwlock_wrlock(lock);
-       else
-               pthread_rwlock_rdlock(lock);
+       pthread_rwlock_destroy(&player->lock_peer);
 
-       pthread_rwlock_unlock(&shutting_down_lock);
-       return true;
+       free(player->name);
+       pthread_rwlock_destroy(&player->lock_auth);
+
+       pthread_rwlock_destroy(&player->lock_pos);
+
+       free(player);
 }
 
-void server_player_init()
+// recv thread
+// called when auth was successful
+static void player_spawn(ServerPlayer *player)
 {
-       pthread_rwlock_init(&shutting_down_lock, NULL);
+       // lock_pos has already been wrlocked by caller
+       if (!database_load_player(player->name, &player->pos, &player->rot)) {
+               player->pos = (v3f64) {0.0, server_terrain_spawn_height() + 0.5, 0.0};
+               player->rot = (v3f32) {0.0f, 0.0f, 0.0f};
+               database_create_player(player->name, player->pos, player->rot);
+       }
 
-       players = list_create((void *) &list_compare_u64);
-       pthread_rwlock_init(&players_lock, NULL);
+       // since this is recv thread, we don't need lock_peer
+       dragonnet_peer_send_ToClientInfo(player->peer, &(ToClientInfo) {
+               .seed = seed,
+               .load_distance = server_config.load_distance,
+       });
+       dragonnet_peer_send_ToClientTimeOfDay(player->peer, &(ToClientTimeOfDay) {
+               .time_of_day = get_time_of_day(),
+       });
+       dragonnet_peer_send_ToClientMovement(player->peer, &(ToClientMovement) {
+               .flight = false,
+               .collision = true,
+               .speed = server_config.movement.speed_normal,
+               .gravity = server_config.movement.gravity,
+               .jump = server_config.movement.jump,
+       });
+       dragonnet_peer_send_ToClientEntityAdd(player->peer, &(ToClientEntityAdd) {
+               .type = ENTITY_LOCALPLAYER,
+               .data = {
+                       .id = player->id,
+                       .pos = player->pos,
+                       .rot = player->rot,
+                       .box = {{-0.3f, 0.0f, -0.3f}, {0.3f, 1.75f, 0.3f}},
+                       .eye = {0.0f, 1.75f, 0.0f},
+                       .nametag = NULL,
+               },
+       });
+}
 
-       names = list_create(&list_compare_string);
-       pthread_rwlock_init(&names_lock, NULL);
+// any thread
+// called when adding, getting or removing a player from the map
+static int cmp_player_id(const Refcount *player, const u64 *id)
+{
+       return u64_cmp(&((ServerPlayer *) player->obj)->id, id);
 }
 
-// list_clear_func callback used on server shutdown to disconnect all clients properly
-static void list_disconnect_player(void *key, unused void *value, unused void *arg)
+// any thread
+// called when adding, getting or removing a player from the players_named Map
+static int cmp_player_name(const Refcount *player, const char *name)
 {
-       ServerPlayer *player = key;
+       // names of players in players_named Map don't change, no lock_auth needed
+       return strcmp(((ServerPlayer *) player->obj)->name, name);
+}
 
-       pthread_t recv_thread = player->peer->recv_thread;
-       server_player_disconnect(player);
-       pthread_join(recv_thread, NULL);
+// main thread
+// called on server startup
+void server_player_init()
+{
+       map_ini(&players);
+       map_ini(&players_named);
 }
 
+// main thread
+// called on server shutdown
 void server_player_deinit()
 {
-       pthread_rwlock_wrlock(&shutting_down_lock);
-       shutting_down = true;
-
-       pthread_rwlock_wrlock(&players_lock);
-       pthread_rwlock_wrlock(&names_lock);
-       pthread_rwlock_unlock(&shutting_down_lock);
-
-       list_clear_func(&players, &list_disconnect_player, NULL);
-       list_clear(&names);
-
-       pthread_rwlock_destroy(&players_lock);
-       pthread_rwlock_destroy(&names_lock);
-       pthread_rwlock_destroy(&shutting_down_lock);
+       // just forget about name -> player mapping
+       map_cnl(&players_named, &refcount_drp, NULL, NULL,          0);
+       // disconnect players and forget about them
+       map_cnl(&players,       &player_drop,  NULL, &refcount_obj, 0);
 }
 
+// accept thread
+// called on new connection
 void server_player_add(DragonnetPeer *peer)
 {
        ServerPlayer *player = malloc(sizeof *player);
 
-       player->id = next_id++;
+       // ID is allocated later in this function
+       player->id = 0;
+       refcount_ini(&player->rc, player, &player_delete);
+
        player->peer = peer;
-       pthread_rwlock_init(&player->ref, NULL);
+       pthread_rwlock_init(&player->lock_peer, NULL);
+
        player->auth = false;
+       // use address as name until auth is done
        player->name = dragonnet_addr_str(peer->raddr);
-       pthread_rwlock_init(&player->auth_lock, NULL);
-       player->pos = (v3f64) {0.0f, 0.0f, 0.0f};
-       pthread_rwlock_init(&player->pos_lock, NULL);
+       pthread_rwlock_init(&player->lock_auth, NULL);
 
-       printf("Connected %s\n", player->name);
+       player->pos = (v3f64) {0.0f, 0.0f, 0.0f};
+       player->rot = (v3f32) {0.0f, 0.0f, 0.0f};
+       pthread_rwlock_init(&player->lock_pos, NULL);
 
-       // accept thread is joined before shutdown, we are guaranteed to obtain the lock
-       pthread_rwlock_wrlock(&players_lock);
+       printf("[access] connected %s\n", player->name);
+       peer->extra = refcount_grb(&player->rc);
 
-       list_put(&players, &player->id, player);
-       peer->extra = player;
+       // keep the search tree somewhat balanced by using random IDs
+       // duplicate IDs are very unlikely, but it doesn't hurt to check
+       // make sure to avoid 0 since it's not a valid ID
+       while (!player->id || !map_add(&players, &player->id, &player->rc, &cmp_player_id, &refcount_inc))
+               player->id = random();
 
-       pthread_rwlock_unlock(&players_lock);
+       // player has been grabbed by Map and peer
+       refcount_drp(&player->rc);
 }
 
+// recv thread
+// called on connection close
 void server_player_remove(DragonnetPeer *peer)
 {
        ServerPlayer *player = peer->extra;
-
-       // only (this) recv thread will modify the auth or name fields, no rdlocks needed
-
-       if (get_lock(&players_lock, true)) {
-               list_delete(&players, &player->id);
-               pthread_rwlock_unlock(&players_lock);
-
-               printf("Disconnected %s\n", player->name);
-       }
-
-       if (player->auth && get_lock(&names_lock, true)) {
-               list_delete(&names, player->name);
-               pthread_rwlock_unlock(&names_lock);
-       }
-
-       pthread_rwlock_wrlock(&player->ref);
-
-       free(player->name);
-
-       pthread_rwlock_destroy(&player->ref);
-       pthread_rwlock_destroy(&player->auth_lock);
-       pthread_rwlock_destroy(&player->pos_lock);
-
-       free(player);
-}
-
-u64 server_player_find(char *name)
-{
-       if (! get_lock(&names_lock, false))
-               return 0;
-
-       u64 *id = list_get(&names, name);
-       return id ? *id : 0;
+       peer->extra = NULL; // technically not necessary, but just in case
+
+       // peer will be deleted - forget about it!
+       pthread_rwlock_wrlock(&player->lock_peer);
+       player->peer = NULL;
+       pthread_rwlock_unlock(&player->lock_peer);
+
+       // only (this) recv thread will modify the auth or name fields, no lock_auth needed
+       // map_del returns false if it was canceled
+       // (we don't want disconnect messages for every player on server shutdown)
+       if (map_del(&players, &player->id, &cmp_player_id, &refcount_drp, NULL, NULL))
+               printf("[access] disconnected %s\n", player->name);
+       if (player->auth)
+               map_del(&players_named, player->name, &cmp_player_name, &refcount_drp, NULL, NULL);
+
+       // peer no longer has a reference to player
+       refcount_drp(&player->rc);
 }
 
+// any thread
 ServerPlayer *server_player_grab(u64 id)
 {
-       if (! id)
-               return NULL;
-
-       if (! get_lock(&players_lock, false))
-               return NULL;
-
-       ServerPlayer *player = list_get(&players, &id);
-       if (player)
-               pthread_rwlock_rdlock(&player->ref);
-
-       pthread_rwlock_unlock(&players_lock);
-
-       return player;
+       // 0 is an invalid ID
+       return id ? map_get(&players, &id, &cmp_player_id, &refcount_grb) : NULL;
 }
 
-void server_player_drop(ServerPlayer *player)
+// any thread
+ServerPlayer *server_player_grab_named(char *name)
 {
-       pthread_rwlock_unlock(&player->ref);
+       // NULL is an invalid name
+       return name ? map_get(&players, name, &cmp_player_name, &refcount_grb) : NULL;
 }
 
+// recv thread
+// called on recv auth packet
 bool server_player_auth(ServerPlayer *player, char *name)
 {
-       if (! get_lock(&names_lock, true))
-               return false;
+       pthread_rwlock_wrlock(&player->lock_auth);
+       pthread_rwlock_wrlock(&player->lock_pos);
+
+       // temporary change name, save old name to either free or reset it if auth fails
+       char *old_name = player->name;
+       player->name = name;
 
-       pthread_rwlock_wrlock(&player->auth_lock);
-       pthread_rwlock_wrlock(&player->pos_lock);
+       bool success = map_add(&players_named, player->name, &player->rc, &cmp_player_name, &refcount_inc);
 
-       bool success = list_put(&names, name, &player->id);
+       printf("[access] authentication %s: %s -> %s\n", success ? "success" : "failure", old_name, player->name);
 
+       // since this is recv thread, we don't need lock_peer
        dragonnet_peer_send_ToClientAuth(player->peer, &(ToClientAuth) {
                .success = success,
        });
 
        if (success) {
-               printf("Authentication %s: %s -> %s\n", success ? "success" : "failure", player->name, name);
-
-               free(player->name);
-               player->name = name;
+               free(old_name);
                player->auth = true;
-
-               if (! database_load_player(player->name, &player->pos)) {
-                       player->pos = (v3f64) {0.0, server_map.spawn_height + 0.5, 0.0};
-                       database_create_player(player->name, player->pos);
-               }
-
-               dragonnet_peer_send_ToClientInfo(player->peer, &(ToClientInfo) {
-                       .seed = seed,
-                       .simulation_distance = server_config.simulation_distance,
-               });
-
-               dragonnet_peer_send_ToClientTimeOfDay(player->peer, &(ToClientTimeOfDay) {
-                       .time_of_day = get_time_of_day(),
-               });
-
-               server_player_send_pos(player);
+               // load player from database and send some initial info
+               player_spawn(player);
+       } else {
+               player->name = old_name;
        }
 
-       pthread_rwlock_unlock(&player->pos_lock);
-       pthread_rwlock_unlock(&player->auth_lock);
-       pthread_rwlock_unlock(&names_lock);
-
+       pthread_rwlock_unlock(&player->lock_pos);
+       pthread_rwlock_unlock(&player->lock_auth);
        return success;
 }
 
+// any thread
 void server_player_disconnect(ServerPlayer *player)
 {
-       dragonnet_peer_shutdown(player->peer);
+       pthread_rwlock_rdlock(&player->lock_peer);
+       // the recv thread will call server_player_remove when the connection was shut down
+       if (player->peer)
+               dragonnet_peer_shutdown(player->peer);
+       pthread_rwlock_unlock(&player->lock_peer);
 }
 
-void server_player_send_pos(ServerPlayer *player)
+// any thread
+void server_player_iterate(void *func, void *arg)
 {
-       dragonnet_peer_send_ToClientPos(player->peer, & (ToClientPos) {
-               .pos = player->pos,
-       });
-}
-
-void server_player_iterate(void (cb)(ServerPlayer *, void *), void *arg)
-{
-       if (! get_lock(&players_lock, false))
-               return;
-
-       ITERATE_LIST(&players, pair) {
-               ServerPlayer *player = pair->value;
-
-               pthread_rwlock_rdlock(&player->auth_lock);
-               if (player->auth)
-                       cb(player, arg);
-               pthread_rwlock_unlock(&player->auth_lock);
-       }
-
-       pthread_rwlock_unlock(&players_lock);
+       map_trv(&players_named, func, arg, &refcount_obj, TRAVERSION_INORDER);
 }
 
 /*