Browse Source

Updated Scufeng. Score rendering. Graceful handling of connection resets. Ability to go back on a multiplayer game.

Aaro Saila 2 tuần trước cách đây
mục cha
commit
ca8b91e9d2

+ 1 - 1
meson.build

@@ -43,7 +43,7 @@ game = executable(
   c_args: [
     c_args,
     '-DFONT_PATH="../assets/LiberationSans-Regular.ttf"',
-    '-D_DEFAULT_SOURCE' # for inet_aton
+    '-D_DEFAULT_SOURCE', # for inet_aton
   ],
   link_args: [
     '-lm'

+ 1 - 0
src/common/GameState_net.c

@@ -25,6 +25,7 @@ void GameState_to_GameStateMsg(const GameState *const src, GameStateMsg *const d
 
     if (!p1) {
         dst->ball.pos_x = 1.0f - dst->ball.pos_x;
+        dst->ball_start_dir_x *= -1.0f;
         dst->ball.dirv_x *= -1.0f;
     }
 }

+ 3 - 9
src/common/game.c

@@ -4,14 +4,6 @@
 #include "scufeng/engine.h"
 #include "scufeng/util.h"
 
-// static const float paddles_w = 0.01f;
-// static const float paddles_h = 0.10f;
-// static const float paddle_l_x = (1.0f / 16);
-// static const float ball_w = paddles_w * 0.5f;
-// static vec2 ball_pos = { 0.5f, 0.5f };
-// static const float paddle_speed = 0.7f;
-// static const float ball_speed = 0.7f;
-
 typedef enum {
     BCR_PADDLE_L_GOAL,
     BCR_PADDLE_R_GOAL,
@@ -72,7 +64,9 @@ GameState GameState_new(
             .speed = params.ball_speed
         },
         .ball_start_pos = { params.ball_pos[0], params.ball_pos[1] },
-        .ball_start_dir = BALL_START_DIR
+        .ball_start_dir = BALL_START_DIR,
+        .score_l = 0,
+        .score_r = 0
     };
     // clang-format on
 

+ 54 - 20
src/common/networking.c

@@ -11,7 +11,7 @@ static const uint8_t fb = 0xfb;
 
 const char *NetMsgType_to_str(const NetMsgType type) {
 #define X(msg) \
-    case msg: \
+    case msg:  \
         return #msg;
 
     switch (type) {
@@ -42,8 +42,21 @@ size_t NetMsgType_to_buf_size(const NetMsgType type) {
     LOG_FATALF("Invalid msg type: %u", type);
 }
 
-// buf is a pointer to one of the packed *Msg structs from networking.h
-void send_msg(FILE *s, const NetMsgType type, const uint8_t *const buf) {
+#define FWRITE_CHECK                                                  \
+    if ((ferror(s) && errno == ECONNRESET) || feof(s)) {              \
+        LOG("Connection reset");                                      \
+        return false;                                                 \
+    } else {                                                          \
+        LOG_FATALF("fwrite failed: (%d) %s", errno, strerror(errno)); \
+    }
+
+/**
+  @param buf Pointer to one of the packed *Msg structs from networking.h
+
+  @return true If succesful.
+  @return false If connection was reset by peer of EOF was encountered.
+*/
+bool send_msg(FILE *s, const NetMsgType type, const uint8_t *const buf) {
     const size_t buf_size = NetMsgType_to_buf_size(type);
 
     uint8_t *buf_cpy = (uint8_t *) malloc(sizeof(uint8_t) * buf_size);
@@ -55,44 +68,54 @@ void send_msg(FILE *s, const NetMsgType type, const uint8_t *const buf) {
     }
 
     if (fwrite(&fb, sizeof(fb), 1, s) < 1) {
-        LOG_FATAL("[send_msg] Failed to write fb");
+        FWRITE_CHECK;
     }
     if (fwrite(&type, sizeof(NetMsgType), 1, s) < 1) {
-        LOG_FATAL("[send_msg] Failed to write msg type");
+        FWRITE_CHECK;
     }
     if (buf_size != 0 && fwrite(buf_cpy, buf_size, 1, s) < 1) {
-        LOG_FATAL("[send_msg] Failed to write msg");
+        FWRITE_CHECK;
     }
     if (fflush(s) != 0) {
-        LOG_FATAL("[send_msg] Failed to flush stream");
+        LOG_ERRORF("[send_msg] Failed to flush stream: (%d) %s", errno, strerror(errno));
+        return false;
     }
 
     free(buf_cpy);
+
+    return true;
 }
 
-static bool recv_until_fb_or_empty_buf(FILE *s) {
+static int recv_until_fb_or_empty_buf(FILE *s) {
     while (true) {
         uint8_t _fb;
         if (fread(&_fb, sizeof(_fb), 1, s) < 1) {
-            if (errno == EWOULDBLOCK) {
-                return false;
+            if (errno == EWOULDBLOCK || errno == ECONNRESET) {
+                return errno;
+            } else if (feof(s)) {
+                return ECONNRESET;
             } else {
                 printf("errno: %d ferror: %d feof: %d\n", errno, ferror(s), feof(s));
-                LOG_FATAL("[recv_msg] Failed to read _fb");
+                LOG_FATAL("[recv_until_fb_or_empty_buf] Failed to read _fb");
             }
         }
         if (_fb != fb) {
             continue;
         } else {
-            return true;
+            return 0;
         }
     }
 }
 
-bool recv_msg_type(FILE *s, NetMsgType *const type) {
-    const bool found_fb = recv_until_fb_or_empty_buf(s);
-    if (!found_fb) {
-        return false;
+/**
+  @return 0 If successful.
+  @return EWOULDBLOCK If there was no data to receive.
+  @return ECONNRESET If connection was reset by peer or EOF was encountered.
+*/
+int recv_msg_type(FILE *s, NetMsgType *const type) {
+    const int err = recv_until_fb_or_empty_buf(s);
+    if (err != 0) {
+        return err;
     }
     if (fread(type, sizeof(NetMsgType), 1, s) < 1) {
         LOG_FATAL("[recv_msg] Failed to read msg type");
@@ -116,19 +139,30 @@ bool recv_msg_type(FILE *s, NetMsgType *const type) {
         LOG_FATALF("[recv_msg_type] Invalid msg type: %u", *type);
     }
 
-    return type;
+    return 0;
 }
 
-// buf is a pointer to one of the packed *Msg structs from networking.h
-void recv_msg(FILE *s, const NetMsgType type, uint8_t *const buf) {
+/**
+  @param buf Pointer to one of the packed *Msg structs from networking.h
+
+  @return true If successful.
+  @return false If connection was reset by peer or EOF was encountered.
+*/
+bool recv_msg(FILE *s, const NetMsgType type, uint8_t *const buf) {
     const size_t msg_len = NetMsgType_to_buf_size(type);
 
     if (fread(buf, msg_len, 1, s) < 1) {
-        LOG_FATAL("[recv_msg] Failed to read msg");
+        if ((ferror(s) && errno == ECONNRESET) || feof(s)) {
+            return false;
+        } else {
+            LOG_FATAL("[recv_msg] Failed to read msg");
+        }
     }
     for (uint8_t *p = buf; p < buf + msg_len; p++) {
         if (*p == fb + SEND_FRAME_MANGLE) {
             *p += RECV_FRAME_MANGLE;
         }
     }
+
+    return true;
 }

+ 3 - 3
src/common/networking.h

@@ -54,6 +54,6 @@ typedef struct [[gnu::packed]] {
 
 const char *NetMsgType_to_str(const NetMsgType type);
 size_t NetMsgType_to_buf_size(const NetMsgType type);
-void send_msg(FILE *s, const NetMsgType type, const uint8_t *const buf);
-bool recv_msg_type(FILE *s, NetMsgType *const type);
-void recv_msg(FILE *s, const NetMsgType type, uint8_t *const buf);
+bool send_msg(FILE *s, const NetMsgType type, const uint8_t *const buf);
+int recv_msg_type(FILE *s, NetMsgType *const type);
+bool recv_msg(FILE *s, const NetMsgType type, uint8_t *const buf);

+ 3 - 4
src/game/MainMenu.c

@@ -1,8 +1,7 @@
 #include <stdio.h>
 
 #include "scufeng/engine.h"
-#include "scufeng/Shape.h"
-#include "scufeng/Scene.h"
+#include "scufeng/Mesh.h"
 #include "cglm/vec3.h"
 
 #include "MainMenu.h"
@@ -50,12 +49,12 @@ Screen MainMenu_tick(MainMenu *const mm) {
     if (screen != PONG_MAIN_MENU) {
         return screen;
     }
-    SEMesh *meshes[] = {
+    const SEMesh *meshes[] = {
         &mm->title_text,
         &mm->sp_text,
         &mm->mp_text
     };
-    se_render_meshes_2d(meshes, sizeof(meshes) / sizeof(meshes[0]));
+    SEMesh_render_multiple_2d(meshes, sizeof(meshes) / sizeof(meshes[0]));
 
     return PONG_MAIN_MENU;
 }

+ 1 - 1
src/game/MainMenu.h

@@ -1,6 +1,6 @@
 #pragma once
 
-#include "scufeng/Shape.h"
+#include "scufeng/Mesh.h"
 
 #include "game_common.h"
 

+ 160 - 66
src/game/MultiPlayer.c

@@ -10,7 +10,6 @@
 #include <fcntl.h>
 
 #include "scufeng/log.h"
-#include "cglm/call/vec2.h"
 
 #include "networking.h"
 #include "MultiPlayer.h"
@@ -18,29 +17,32 @@
 #include "game_common.h"
 #include "GameState_net.h"
 
-MultiPlayer MultiPlayer_alloc(
+static bool handle_game_state_msg(MultiPlayer *const mp) {
+    GameStateMsg msg;
+    if (!recv_msg(mp->conn_s, NET_MSG_GAME_STATE, (uint8_t *) &msg)) {
+        return false;
+    }
+    GameStateMsg_to_GameState(&msg, &mp->state);
+    return true;
+}
+
+bool MultiPlayer_alloc(
+    MultiPlayer *const mp,
     const unsigned int screen_w,
     const unsigned int screen_h,
     const char *server_host,
     const uint16_t server_port
 ) {
-    MultiPlayer mp;
-
-    mp.screen_w = screen_w;
-    mp.screen_h = screen_h;
-
-    // mp.state = GameState_new(game_default_params, false);
-    mp.shapes = GameShapes_alloc(screen_w, screen_h, game_default_params);
-
     struct protoent *proto = getprotobyname("tcp");
-    mp.sock = socket(AF_INET, SOCK_STREAM, proto->p_proto);
-    if (mp.sock == -1) {
+    mp->sock = socket(AF_INET, SOCK_STREAM, proto->p_proto);
+    if (mp->sock == -1) {
         LOG_FATALF("socket failed: %d %s", errno, strerror(errno));
     }
 
     struct in_addr server_addr;
     int res = inet_aton(server_host, &server_addr);
     if (res == 0) {
+        close(mp->sock);
         LOG_FATAL("inet_aton failed");
     }
 
@@ -51,30 +53,47 @@ MultiPlayer MultiPlayer_alloc(
     };
 
     res = connect(
-        mp.sock,
+        mp->sock,
         (struct sockaddr *) &server_sockaddr,
         sizeof(server_sockaddr)
     );
     if (res == -1) {
-        LOG_FATALF("connect failed: %d %s", errno, strerror(errno));
+        close(mp->sock);
+        LOG_ERRORF("connect failed: %d %s", errno, strerror(errno));
+        return false;
     }
 
     LOG("Connected to server");
 
-    mp.conn_s = fdopen(mp.sock, "r+");
-    if (mp.conn_s == nullptr) {
-        LOG_FATALF("fdopen failed: (%d) %s", errno, strerror(errno));
+    mp->conn_s = fdopen(mp->sock, "r+");
+    if (mp->conn_s == nullptr) {
+        close(mp->sock);
+        LOG_ERRORF("fdopen failed: (%d) %s", errno, strerror(errno));
+        return false;
     }
 
     NetMsgType msg_type;
-    recv_msg_type(mp.conn_s, &msg_type);
+    if (recv_msg_type(mp->conn_s, &msg_type) == ECONNRESET) {
+        fclose(mp->conn_s);
+        close(mp->sock);
+        LOG_ERROR("Connection reset");
+        return false;
+    }
     if (msg_type != NET_MSG_IS_PLAYER_1) {
-        LOG_FATALF("Did not get IS_PLAYER_1 msg, got: %u", msg_type);
+        fclose(mp->conn_s);
+        close(mp->sock);
+        LOG_ERRORF("Did not get IS_PLAYER_1 msg, got: %u", msg_type);
+        return false;
     }
     IsPlayer1Msg p1_msg = { 0 };
-    recv_msg(mp.conn_s, NET_MSG_IS_PLAYER_1, (uint8_t *) &p1_msg);
-    mp.is_player_1 = (bool) p1_msg.player1;
-    if (mp.is_player_1) {
+    if (!recv_msg(mp->conn_s, NET_MSG_IS_PLAYER_1, (uint8_t *) &p1_msg)) {
+        fclose(mp->conn_s);
+        close(mp->sock);
+        LOG_ERROR("Connection reset");
+        return false;
+    }
+    mp->is_player_1 = (bool) p1_msg.player1;
+    if (mp->is_player_1) {
         LOG("I am player 1");
     } else {
         LOG("I am player 2");
@@ -82,23 +101,66 @@ MultiPlayer MultiPlayer_alloc(
 
     LOG("Waiting for game start");
 
-    recv_msg_type(mp.conn_s, &msg_type);
+    mp->state = GameState_new(game_default_params, false);
+
+    if (recv_msg_type(mp->conn_s, &msg_type) == ECONNRESET) {
+        fclose(mp->conn_s);
+        close(mp->sock);
+        LOG_ERROR("Connection reset");
+        return false;
+    }
+    if (msg_type != NET_MSG_GAME_STATE) {
+        fclose(mp->conn_s);
+        close(mp->sock);
+        LOG_FATALF("[MultiPlayer_alloc] Did not get GAME_STATE msg, got: %u", msg_type);
+    }
+    if (!handle_game_state_msg(mp)) {
+        fclose(mp->conn_s);
+        close(mp->sock);
+        LOG_ERROR("Connection reset");
+        return false;
+    }
+
+    if (recv_msg_type(mp->conn_s, &msg_type) == ECONNRESET) {
+        fclose(mp->conn_s);
+        close(mp->sock);
+        LOG_ERROR("Connection reset");
+        return false;
+    }
     if (msg_type != NET_MSG_GAME_START) {
+        fclose(mp->conn_s);
+        close(mp->sock);
         LOG_FATALF("[MultiPlayer_alloc] Did not get GAME_START msg, got: %u", msg_type);
     }
 
-    if (fcntl(mp.sock, F_SETFL, O_NONBLOCK) != 0) {
-        LOG_FATALF("fcntl failed on mp.sock: (%d) %s", errno, strerror(errno));
+    if (!send_msg(mp->conn_s, NET_MSG_GAME_START, nullptr)) {
+        fclose(mp->conn_s);
+        close(mp->sock);
+        LOG_ERROR("Connection reset");
+        return false;
     }
 
+    if (fcntl(mp->sock, F_SETFL, O_NONBLOCK) != 0) {
+        fclose(mp->conn_s);
+        close(mp->sock);
+        LOG_ERRORF("fcntl failed on mp->sock: (%d) %s", errno, strerror(errno));
+        return false;
+    }
+
+    LOG("Game started");
+
+    mp->screen_w = screen_w;
+    mp->screen_h = screen_h;
+    mp->shapes = GameShapes_alloc(screen_w, screen_h, game_default_params);
+
     return mp;
 }
 
 void MultiPlayer_free(MultiPlayer *const mp) {
     fclose(mp->conn_s);
     mp->conn_s = nullptr;
-    close(mp->conn);
-    mp->conn = 0;
+    // close(mp->conn);
+    // mp->conn = 0;
     close(mp->sock);
     mp->sock = 0;
 
@@ -114,12 +176,6 @@ static void handle_paddle_pos_msg(MultiPlayer *const mp) {
     mp->state.paddle_r.dirv[1] = msg.r_dirv_y;
 }
 
-static void handle_game_state_msg(MultiPlayer *const mp) {
-    GameStateMsg msg;
-    recv_msg(mp->conn_s, NET_MSG_GAME_STATE, (uint8_t *) &msg);
-    GameStateMsg_to_GameState(&msg, &mp->state);
-}
-
 static void handle_ball_update_msg(MultiPlayer *const mp) {
     BallUpdateMsg msg;
     recv_msg(mp->conn_s, NET_MSG_BALL_UPDATE, (uint8_t *) &msg);
@@ -134,49 +190,82 @@ static void handle_score_update_msg(MultiPlayer *const mp) {
     recv_msg(mp->conn_s, NET_MSG_SCORE_UPDATE, (uint8_t *) &msg);
     mp->state.score_l = msg.l_score;
     mp->state.score_r = msg.r_score;
+    LOGF("score_l: %d score_r: %d", mp->state.score_l, mp->state.score_r);
 }
 
-void MultiPlayer_tick(MultiPlayer *const mp, const double delta) {
-    NetMsgType msg_type;
-    while (recv_msg_type(mp->conn_s, &msg_type)) {
-        // LOGF("Got message: %s", NetMsgType_to_str(msg_type));
-        switch (msg_type) {
-        case NET_MSG_PADDLE_CHANGE_DIR:
-            break;
-        case NET_MSG_PADDLE_POS:
-            handle_paddle_pos_msg(mp);
-            break;
-        case NET_MSG_GAME_STATE:
-            handle_game_state_msg(mp);
-            break;
-        case NET_MSG_IS_PLAYER_1:
-        case NET_MSG_GAME_START:
-            break;
-        case NET_MSG_BALL_UPDATE:
-            handle_ball_update_msg(mp);
-            break;
-        case NET_MSG_SCORE_UPDATE:
-            handle_score_update_msg(mp);
-            break;
-        }
+static void handle_input(
+    const MultiPlayer *const mp,
+    bool *dir_changed,
+    SEDirection *new_dir,
+    bool *quit
+) {
+    if (se_get_key(SE_KEY_ESCAPE) == SE_KEY_ACTION_PRESS) {
+        *quit = true;
+        return;
     }
 
-    SEDirection new_dir = SE_NONE;
-    bool dir_changed = false;
+    *new_dir = SE_NONE;
+    *dir_changed = false;
     const SEDirection current_dir = GameState_paddle_l_dir_get(&mp->state);
     if (se_get_key(SE_KEY_W) == SE_KEY_ACTION_PRESS) {
         if (current_dir != SE_UP) {
-            new_dir = SE_UP;
-            dir_changed = true;
+            *new_dir = SE_UP;
+            *dir_changed = true;
         }
     } else if (se_get_key(SE_KEY_S) == SE_KEY_ACTION_PRESS) {
         if (current_dir != SE_DOWN) {
-            new_dir = SE_DOWN;
-            dir_changed = true;
+            *new_dir = SE_DOWN;
+            *dir_changed = true;
         }
     } else if (current_dir != SE_NONE) {
-        new_dir = SE_NONE;
-        dir_changed = true;
+        *new_dir = SE_NONE;
+        *dir_changed = true;
+    }
+}
+
+Screen MultiPlayer_tick(MultiPlayer *const mp, const double delta) {
+    NetMsgType msg_type;
+    while (true) {
+        const int res = recv_msg_type(mp->conn_s, &msg_type);
+        if (res == 0) {
+            switch (msg_type) {
+            case NET_MSG_PADDLE_CHANGE_DIR:
+                break;
+            case NET_MSG_PADDLE_POS:
+                handle_paddle_pos_msg(mp);
+                break;
+            case NET_MSG_GAME_STATE:
+                handle_game_state_msg(mp);
+                break;
+            case NET_MSG_IS_PLAYER_1:
+            case NET_MSG_GAME_START:
+                break;
+            case NET_MSG_BALL_UPDATE:
+                handle_ball_update_msg(mp);
+                break;
+            case NET_MSG_SCORE_UPDATE:
+                handle_score_update_msg(mp);
+                break;
+            }
+            break;
+        } else if (res == ECONNRESET) {
+            LOG_ERROR("Connection reset");
+            return PONG_MAIN_MENU;
+        } else if (res == EWOULDBLOCK) {
+            break;
+        } else {
+            LOG_FATALF("[MultiPlayer_tick] Unhandled return value: %d", res);
+        }
+
+        // LOGF("Got message: %s", NetMsgType_to_str(msg_type));
+    }
+
+    bool dir_changed = false;
+    SEDirection new_dir;
+    bool quit = false;
+    handle_input(mp, &dir_changed, &new_dir, &quit);
+    if (quit) {
+        return PONG_MAIN_MENU;
     }
 
     if (dir_changed) {
@@ -185,10 +274,15 @@ void MultiPlayer_tick(MultiPlayer *const mp, const double delta) {
             .dir = (uint8_t) new_dir
         };
 
-        send_msg(mp->conn_s, NET_MSG_PADDLE_CHANGE_DIR, (uint8_t *) &msg);
+        if (!send_msg(mp->conn_s, NET_MSG_PADDLE_CHANGE_DIR, (uint8_t *) &msg)) {
+            LOG_ERROR("Connection reset");
+            return PONG_MAIN_MENU;
+        }
     }
 
     GameState_update(&mp->state, delta);
     GameShapes_update(&mp->shapes, &mp->state, mp->screen_w, mp->screen_h);
-    GameShapes_render(&mp->shapes);
+    GameShapes_render(&mp->shapes, &mp->state, mp->screen_w, mp->screen_h);
+
+    return PONG_MULTIPLAYER;
 }

+ 3 - 2
src/game/MultiPlayer.h

@@ -17,11 +17,12 @@ typedef struct {
     FILE *conn_s;
 } MultiPlayer;
 
-MultiPlayer MultiPlayer_alloc(
+bool MultiPlayer_alloc(
+    MultiPlayer *const mp,
     const unsigned int screen_w,
     const unsigned int screen_h,
     const char *server_host,
     const uint16_t server_port
 );
 void MultiPlayer_free(MultiPlayer *const mp);
-void MultiPlayer_tick(MultiPlayer *const mp, const double delta);
+Screen MultiPlayer_tick(MultiPlayer *const mp, const double delta);

+ 9 - 88
src/game/SinglePlayer.c

@@ -1,7 +1,6 @@
-#include "cglm/vec4.h"
-#include "game.h"
-#include "scufeng/Scene.h"
+#include "scufeng/Mesh.h"
 
+#include "game.h"
 #include "SinglePlayer.h"
 #include "game_common.h"
 
@@ -14,24 +13,8 @@ SinglePlayer SinglePlayer_alloc(
         .screen_w = screen_w,
         .screen_h = screen_h,
         .state = GameState_new(params, true),
-        .paddle_l = SEMesh_rect_color_alloc(GLM_VEC4_ONE),
-        .paddle_r = SEMesh_rect_color_alloc(GLM_VEC4_ONE),
-        .ball = SEMesh_rect_color_alloc(GLM_VEC4_ONE),
+        .shapes = GameShapes_alloc(screen_w, screen_h, params)
     };
-    sp.paddle_l.transform.scale[0] = screen_w * params.paddles_w;
-    sp.paddle_l.transform.scale[1] = screen_h * params.paddles_h;
-    sp.paddle_l.transform.pos[0] = screen_w * params.paddle_l_x;
-    sp.paddle_l.transform.pos[1] = screen_h / 2.0f;
-
-    sp.paddle_r.transform.scale[0] = screen_w * params.paddles_w;
-    sp.paddle_r.transform.scale[1] = screen_h * params.paddles_h;
-    sp.paddle_r.transform.pos[0] = screen_w * (1.0f - params.paddle_l_x);
-    sp.paddle_r.transform.pos[1] = screen_h / 2.0f;
-
-    sp.ball.transform.scale[0] = screen_w * params.ball_w;
-    sp.ball.transform.scale[1] = sp.ball.transform.scale[0];
-    sp.ball.transform.pos[0] = screen_w * params.ball_pos[0];
-    sp.ball.transform.pos[1] = screen_h * params.ball_pos[1];
 
     return sp;
 }
@@ -44,66 +27,16 @@ SinglePlayer SinglePlayer_alloc_default(
         .screen_w = screen_w,
         .screen_h = screen_h,
         .state = GameState_new(game_default_params, true),
-        .paddle_l = SEMesh_rect_color_alloc(GLM_VEC4_ONE),
-        .paddle_r = SEMesh_rect_color_alloc(GLM_VEC4_ONE),
-        .ball = SEMesh_rect_color_alloc(GLM_VEC4_ONE),
+        .shapes = GameShapes_alloc(screen_w, screen_h, game_default_params)
     };
 
-    const float paddles_w = game_default_params.paddles_w;
-    const float paddles_h = game_default_params.paddles_h;
-    const float paddle_l_x = game_default_params.paddle_l_x;
-    const float ball_w = game_default_params.ball_w;
-    const float *ball_pos = game_default_params.ball_pos; // Scuffed but here for conveniance for now
-
-    sp.paddle_l.transform.scale[0] = screen_w * paddles_w;
-    sp.paddle_l.transform.scale[1] = screen_h * paddles_h;
-    sp.paddle_l.transform.pos[0] = screen_w * paddle_l_x;
-    sp.paddle_l.transform.pos[1] = screen_h / 2.0f;
-
-    sp.paddle_r.transform.scale[0] = screen_w * paddles_w;
-    sp.paddle_r.transform.scale[1] = screen_h * paddles_h;
-    sp.paddle_r.transform.pos[0] = screen_w * (1.0f - paddle_l_x);
-    sp.paddle_r.transform.pos[1] = screen_h / 2.0f;
-
-    sp.ball.transform.scale[0] = screen_w * ball_w;
-    sp.ball.transform.scale[1] = sp.ball.transform.scale[0];
-    sp.ball.transform.pos[0] = screen_w * ball_pos[0];
-    sp.ball.transform.pos[1] = screen_h * ball_pos[1];
-
     return sp;
 }
 
 void SinglePlayer_free(SinglePlayer *sp) {
-    SEMesh_free(&sp->paddle_l);
-    SEMesh_free(&sp->paddle_r);
-    SEMesh_free(&sp->ball);
-}
-
-static void pos_convert(
-    vec3 render_pos,
-    const vec3 state_pos,
-    const unsigned int screen_w,
-    const unsigned int screen_h
-) {
-    render_pos[0] = state_pos[0] * screen_w;
-    render_pos[1] = (1.0f - state_pos[1]) * screen_h;
-}
-
-static void update_shapes(
-    const GameState *const state,
-    SEMesh *const paddle_l,
-    SEMesh *const paddle_r,
-    SEMesh *const ball,
-    const unsigned int screen_w,
-    const unsigned int screen_h
-) {
-
-    // Paddles
-    pos_convert(paddle_l->transform.pos, state->paddle_l.pos, screen_w, screen_h);
-    pos_convert(paddle_r->transform.pos, state->paddle_r.pos, screen_w, screen_h);
-
-    // Ball
-    pos_convert(ball->transform.pos, state->ball.pos, screen_w, screen_h);
+    SEMesh_free(&sp->shapes.paddle_l);
+    SEMesh_free(&sp->shapes.paddle_r);
+    SEMesh_free(&sp->shapes.ball);
 }
 
 static bool key_input_handle(GameState *const state) {
@@ -127,20 +60,8 @@ Screen SinglePlayer_tick(SinglePlayer *const sp, const double delta) {
         return PONG_MAIN_MENU;
     }
     GameState_update(&sp->state, delta);
-    update_shapes(
-        &sp->state,
-        &sp->paddle_l,
-        &sp->paddle_r,
-        &sp->ball,
-        sp->screen_w,
-        sp->screen_h
-    );
-    SEMesh *meshes[] = {
-        &sp->paddle_l,
-        &sp->paddle_r,
-        &sp->ball
-    };
-    se_render_meshes_2d(meshes, sizeof(meshes) / sizeof(meshes[0]));
+    GameShapes_update(&sp->shapes, &sp->state, sp->screen_w, sp->screen_h);
+    GameShapes_render(&sp->shapes, &sp->state, sp->screen_w, sp->screen_h);
 
     return PONG_SINGLEPLAYER;
 }

+ 1 - 5
src/game/SinglePlayer.h

@@ -1,7 +1,5 @@
 #pragma once
 
-#include "scufeng/Shape.h"
-
 #include "game.h"
 #include "game_common.h"
 
@@ -9,9 +7,7 @@ typedef struct {
     unsigned int screen_w;
     unsigned int screen_h;
     GameState state;
-    SEMesh paddle_l;
-    SEMesh paddle_r;
-    SEMesh ball;
+    GameShapes shapes;
 } SinglePlayer;
 
 SinglePlayer SinglePlayer_alloc(

+ 34 - 5
src/game/game_common.c

@@ -1,5 +1,9 @@
+#include <stdio.h>
+
 #include "cglm/vec4.h"
-#include "scufeng/Scene.h"
+#include "cglm/vec3.h"
+#include "scufeng/Mesh.h"
+#include "scufeng/text.h"
 
 #include "game_common.h"
 #include "game.h"
@@ -59,17 +63,42 @@ GameShapes GameShapes_alloc(
     return shapes;
 }
 
-void GameShapes_free(GameShapes *shapes) {
+void GameShapes_free(GameShapes *const shapes) {
     SEMesh_free(&shapes->paddle_l);
     SEMesh_free(&shapes->paddle_r);
     SEMesh_free(&shapes->ball);
 }
 
-void GameShapes_render(GameShapes *shapes) {
-    SEMesh *meshes[] = {
+void GameShapes_render(
+    GameShapes *const shapes,
+    const GameState *const state,
+    const unsigned int screen_w,
+    const unsigned int screen_h
+) {
+    const SEMesh *meshes[] = {
         &shapes->paddle_l,
         &shapes->paddle_r,
         &shapes->ball
     };
-    se_render_meshes_2d(meshes, sizeof(meshes) / sizeof(meshes[0]));
+    SEMesh_render_multiple_2d(meshes, sizeof(meshes) / sizeof(meshes[0]));
+
+    const float score_w = 0.25f;
+    const float score_h = 0.9f;
+    char score_txt[4] = { 0 };
+    snprintf(score_txt, 4, "%d", state->score_l);
+    se_text_render(
+        score_txt,
+        screen_w * score_w,
+        screen_h * score_h,
+        1.0f,
+        GLM_VEC3_ONE
+    );
+    snprintf(score_txt, 4, "%d", state->score_r);
+    se_text_render(
+        score_txt,
+        screen_w * (1.0f - score_w),
+        screen_h * score_h,
+        1.0f,
+        GLM_VEC3_ONE
+    );
 }

+ 7 - 2
src/game/game_common.h

@@ -1,6 +1,6 @@
 #pragma once
 
-#include "scufeng/Shape.h"
+#include "scufeng/Mesh.h"
 
 #include "game.h"
 
@@ -30,4 +30,9 @@ GameShapes GameShapes_alloc(
     GameStateParameters params
 );
 void GameShapes_free(GameShapes *shapes);
-void GameShapes_render(GameShapes *shapes);
+void GameShapes_render(
+    GameShapes *const shapes,
+    const GameState *const state,
+    const unsigned int screen_w,
+    const unsigned int screen_h
+);

+ 15 - 3
src/game/pong.c

@@ -1,14 +1,19 @@
+#include <signal.h>
+
 #include "scufeng/engine.h"
 #include "scufeng/text.h"
 
 #include "MainMenu.h"
 #include "SinglePlayer.h"
 #include "MultiPlayer.h"
+#include "src/game/game_common.h"
 
 static const unsigned int screen_w = 1200 / 2;
 static const unsigned int screen_h = 600;
 
 int main() {
+    signal(SIGPIPE, SIG_IGN);
+
     se_init(true);
     se_window_create(screen_w, screen_h, "Pong", -1);
     se_swap_interval(0);
@@ -21,7 +26,7 @@ int main() {
     SinglePlayer sp;
     MultiPlayer mp;
 
-    double last_frame_time = 0.0;
+    double last_frame_time = se_get_time();
     while (!se_window_should_close()) {
         const double current_time = se_get_time();
         const double delta = current_time - last_frame_time;
@@ -42,8 +47,12 @@ int main() {
                 );
                 break;
             case PONG_MULTIPLAYER:
-                mp = MultiPlayer_alloc(screen_w, screen_h, "127.0.0.1", 8080);
+            {
+                if (!MultiPlayer_alloc(&mp, screen_w, screen_h, "127.0.0.1", 8080)) {
+                    current_screen = PONG_MAIN_MENU;
+                }
                 break;
+            }
             default:
                 break;
             }
@@ -56,7 +65,10 @@ int main() {
             break;
 
         case PONG_MULTIPLAYER:
-            MultiPlayer_tick(&mp, delta);
+            current_screen = MultiPlayer_tick(&mp, delta);
+            if (current_screen == PONG_MAIN_MENU) {
+                MultiPlayer_free(&mp);
+            }
             break;
         }
 

+ 115 - 115
src/server/server.c

@@ -8,6 +8,7 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <stdint.h>
+#include <signal.h>
 
 #include "scufeng/engine.h"
 #include "scufeng/log.h"
@@ -36,7 +37,7 @@ static SCDArray_NetMsgType out_msgs;
 
 SCDARRAY_IMPL(NetMsgType, NetMsgType)
 
-static void send_out_msgs() {
+static bool send_out_msgs() {
     for (size_t i = 0; i < out_msgs.len; i++) {
         const NetMsgType *const type = SCDArray_NetMsgType_at(&out_msgs, i);
         switch (*type) {
@@ -49,13 +50,17 @@ static void send_out_msgs() {
                 .r_pos_y = game_state.paddle_r.pos[1],
                 .r_dirv_y = game_state.paddle_r.dirv[1]
             };
-            send_msg(p1_conn, NET_MSG_PADDLE_POS, (uint8_t *) &msg);
+            if (!send_msg(p1_conn, NET_MSG_PADDLE_POS, (uint8_t *) &msg)) {
+                return false;
+            }
             // p2
             msg.l_pos_y = game_state.paddle_r.pos[1];
             msg.l_dirv_y = game_state.paddle_r.dirv[1];
             msg.r_pos_y = game_state.paddle_l.pos[1];
             msg.r_dirv_y = game_state.paddle_l.dirv[1];
-            send_msg(p2_conn, NET_MSG_PADDLE_POS, (uint8_t *) &msg);
+            if (!send_msg(p2_conn, NET_MSG_PADDLE_POS, (uint8_t *) &msg)) {
+                return false;
+            }
             break;
         }
         case NET_MSG_PADDLE_CHANGE_DIR:
@@ -71,11 +76,15 @@ static void send_out_msgs() {
                 .ball_dirv_x = game_state.ball.dirv[0],
                 .ball_dirv_y = game_state.ball.dirv[1],
             };
-            send_msg(p1_conn, NET_MSG_BALL_UPDATE, (uint8_t *) &msg);
+            if (!send_msg(p1_conn, NET_MSG_BALL_UPDATE, (uint8_t *) &msg)) {
+                return false;
+            }
 
             msg.ball_pos_x = 1.0f - msg.ball_pos_x;
             msg.ball_dirv_x *= -1.0f;
-            send_msg(p2_conn, NET_MSG_BALL_UPDATE, (uint8_t *) &msg);
+            if (!send_msg(p2_conn, NET_MSG_BALL_UPDATE, (uint8_t *) &msg)) {
+                return false;
+            }
             break;
         }
         case NET_MSG_SCORE_UPDATE:
@@ -84,26 +93,32 @@ static void send_out_msgs() {
                 .l_score = game_state.score_l,
                 .r_score = game_state.score_r
             };
-            send_msg(p1_conn, NET_MSG_SCORE_UPDATE, (uint8_t *) &msg);
+            if (!send_msg(p1_conn, NET_MSG_SCORE_UPDATE, (uint8_t *) &msg)) {
+                return false;
+            }
 
             msg.l_score = game_state.score_r;
             msg.r_score = game_state.score_l;
-            send_msg(p2_conn, NET_MSG_SCORE_UPDATE, (uint8_t *) &msg);
+            if (!send_msg(p2_conn, NET_MSG_SCORE_UPDATE, (uint8_t *) &msg)) {
+                return false;
+            }
             break;
         }
         }
     }
 
     SCDArray_NetMsgType_clear_no_resize(&out_msgs);
+
+    return true;
 }
 
-static void send_game_state(FILE *conn, const bool p1) {
+static bool send_game_state(FILE *conn, const bool p1) {
     GameStateMsg msg;
     GameState_to_GameStateMsg(&game_state, &msg, p1);
-    send_msg(conn, NET_MSG_GAME_STATE, (uint8_t *) &msg);
+    return send_msg(conn, NET_MSG_GAME_STATE, (uint8_t *) &msg);
 }
 
-static void server_lobby(const int sock) {
+static bool server_lobby(const int sock) {
     assert_scufcont_success(SCDArray_NetMsgType_alloc(&out_msgs));
     game_state = GameState_new(game_default_params, false);
 
@@ -111,9 +126,6 @@ static void server_lobby(const int sock) {
     if (player1 == -1) {
         LOG_FATALF("accept failed: %s", strerror(errno));
     }
-    if (fcntl(player1, F_SETFL, O_NONBLOCK) != 0) {
-        LOG_FATALF("fcntl failed on player1: (%d) %s", errno, strerror(errno));
-    }
     printf("Player 1 connected\n");
 
     p1_conn = fdopen(player1, "r+");
@@ -124,15 +136,16 @@ static void server_lobby(const int sock) {
     IsPlayer1Msg p1_msg = {
         .player1 = 1u
     };
-    send_msg(p1_conn, NET_MSG_IS_PLAYER_1, (uint8_t *) &p1_msg);
+    if (!send_msg(p1_conn, NET_MSG_IS_PLAYER_1, (uint8_t *) &p1_msg)) {
+        close(player1);
+        SCDArray_NetMsgType_free(&out_msgs);
+        return false;
+    }
 
     int player2 = accept(sock, nullptr, nullptr);
     if (player2 == -1) {
         LOG_FATALF("accept failed: %s", strerror(errno));
     }
-    if (fcntl(player2, F_SETFL, O_NONBLOCK) != 0) {
-        LOG_FATALF("fcntl failed on player2: (%d) %s", errno, strerror(errno));
-    }
     printf("Player 2 connected\n");
 
     p2_conn = fdopen(player2, "r+");
@@ -141,7 +154,14 @@ static void server_lobby(const int sock) {
     }
 
     p1_msg.player1 = 0u;
-    send_msg(p2_conn, NET_MSG_IS_PLAYER_1, (uint8_t *) &p1_msg);
+    if (!send_msg(p2_conn, NET_MSG_IS_PLAYER_1, (uint8_t *) &p1_msg)) {
+        close(player1);
+        close(player2);
+        SCDArray_NetMsgType_free(&out_msgs);
+        return false;
+    }
+
+    return true;
 }
 
 static void handle_paddle_change_dir_msg(FILE *conn, const bool p1) {
@@ -157,10 +177,13 @@ static void handle_paddle_change_dir_msg(FILE *conn, const bool p1) {
     assert_scufcont_success(SCDArray_NetMsgType_push_back(&out_msgs, &type));
 }
 
-static void handle_player_input(const bool p1) {
+static bool handle_player_input(const bool p1) {
     FILE *conn = p1 ? p1_conn : p2_conn;
     NetMsgType msg_type;
-    if (recv_msg_type(conn, &msg_type)) {
+    const int res = recv_msg_type(conn, &msg_type);
+    switch (res) {
+    case 0:
+    {
         switch (msg_type) {
         case NET_MSG_PADDLE_CHANGE_DIR:
             handle_paddle_change_dir_msg(conn, p1);
@@ -173,18 +196,61 @@ static void handle_player_input(const bool p1) {
         case NET_MSG_SCORE_UPDATE:
             break;
         }
+        return true;
     }
+    case EWOULDBLOCK:
+        return true;
+    case ECONNRESET:
+        return false;
+    }
+
+    return true;
 }
 
 static void server_game() {
     se_fps_lock_set_fps(60);
 
-    send_msg(p1_conn, NET_MSG_GAME_START, nullptr);
-    send_msg(p2_conn, NET_MSG_GAME_START, nullptr);
-    send_game_state(p1_conn, true);
-    send_game_state(p2_conn, false);
+    if (!send_game_state(p1_conn, true)) {
+        goto cleanup;
+    }
+    if (!send_game_state(p2_conn, false)) {
+        goto cleanup;
+    }
+    if (!send_msg(p1_conn, NET_MSG_GAME_START, nullptr)) {
+        goto cleanup;
+    }
+    if (!send_msg(p2_conn, NET_MSG_GAME_START, nullptr)) {
+        goto cleanup;
+    }
+
+    NetMsgType msg_type;
 
-    double last_frame_time_s = 0.0;
+    if (recv_msg_type(p1_conn, &msg_type) == ECONNRESET) {
+        goto cleanup;
+    }
+    if (msg_type != NET_MSG_GAME_START) {
+        LOG_ERRORF("Did not receive GAME_START, got: %s\n", NetMsgType_to_str(msg_type));
+        goto cleanup;
+    }
+
+    if (recv_msg_type(p2_conn, &msg_type) == ECONNRESET) {
+        goto cleanup;
+    }
+    if (msg_type != NET_MSG_GAME_START) {
+        LOG_ERRORF("Did not receive GAME_START, got: %s\n", NetMsgType_to_str(msg_type));
+        goto cleanup;
+    }
+
+    if (fcntl(fileno(p1_conn), F_SETFL, O_NONBLOCK) != 0) {
+        LOG_FATALF("fcntl failed on player1: (%d) %s", errno, strerror(errno));
+    }
+    if (fcntl(fileno(p2_conn), F_SETFL, O_NONBLOCK) != 0) {
+        LOG_FATALF("fcntl failed on player2: (%d) %s", errno, strerror(errno));
+    }
+
+    LOG("Game start");
+
+    double last_frame_time_s = se_get_time();
     while (true) {
         const double current_time_s = se_get_time();
         const double delta = current_time_s - last_frame_time_s;
@@ -192,14 +258,15 @@ static void server_game() {
         se_fps_lock_frame_start_s(current_time_s);
 
         // Player input
-        handle_player_input(true);
-        handle_player_input(false);
+        if (!handle_player_input(true)) {
+            goto cleanup;
+        }
+        if (!handle_player_input(false)) {
+            goto cleanup;
+        }
 
         // Tick
         const GameStateEvent gs_event = GameState_update(&game_state, delta);
-        // if (gs_event != GSEVENT_NONE) {
-        //     LOGF("GameStateEvent: %s", GameStateEvent_to_str(gs_event));
-        // }
         switch (gs_event) {
         case GSEVENT_BALL_DIR_CHANGE:
         {
@@ -212,6 +279,7 @@ static void server_game() {
         case GSEVENT_L_SCORE:
         case GSEVENT_R_SCORE:
         {
+            LOG("Goal");
             NetMsgType type = NET_MSG_SCORE_UPDATE;
             assert_scufcont_success(SCDArray_NetMsgType_push_back(&out_msgs, &type));
             type = NET_MSG_BALL_UPDATE;
@@ -221,14 +289,24 @@ static void server_game() {
         }
 
         // Player output
-        send_out_msgs();
+        if (!send_out_msgs()) {
+            goto cleanup;
+        }
 
         // Wait
         se_fps_lock_wait();
     }
+
+cleanup:
+    LOG("Cleaning up");
+    fclose(p1_conn);
+    fclose(p2_conn);
+    SCDArray_NetMsgType_free(&out_msgs);
 }
 
 int main() {
+    signal(SIGPIPE, SIG_IGN);
+
     se_init(true);
 
     struct protoent *proto = getprotobyname("tcp");
@@ -272,96 +350,18 @@ int main() {
     while (true) {
         switch (server_state) {
         case SS_LOBBY:
-            server_lobby(sock);
-            server_state = SS_GAME;
+            LOG("SS_LOBBY");
+            if (server_lobby(sock)) {
+                server_state = SS_GAME;
+            }
             break;
         case SS_GAME:
+            LOG("SS_GAME");
             server_game();
+            server_state = SS_LOBBY;
             break;
         }
     }
 
-    // printf("Player connected\n");
-    //
-    // FILE *conn_s = fdopen(conn, "r+");
-    // if (conn_s == nullptr) {
-    //     LOG_FATALF("fdopen failed: (%d) %s", errno, strerror(errno));
-    // }
-    //
-    // while (true) {
-    //     printf("Reading msg_len\n");
-    //     size_t msg_len;
-    //     size_t bytes_read = fread(&msg_len, sizeof(msg_len), 1, conn_s);
-    //     if (bytes_read < 1) {
-    //         LOG_FATALF("fread failed to read msg_len %zu", bytes_read);
-    //     }
-    //     printf("msg_len: %zu\n", msg_len);
-    //
-    //     printf("\nReading msg\n");
-    //     char *msg = (char *) malloc(msg_len + 1);
-    //     bytes_read = fread(msg, msg_len, 1, conn_s);
-    //     if (bytes_read < 1) {
-    //         LOG_FATALF("fread failed to read msg %zu", bytes_read);
-    //     }
-    //     msg[msg_len] = '\0';
-    //
-    //     printf("msg: %s\n\n", msg);
-    //
-    //     free(msg);
-    // }
-    //
-    // fclose(conn_s);
-    // close(conn);
-    // close(sock);
-
-    // char inter_buf[1024] = {0};
-    // const size_t inter_buf_size = sizeof(inter_buf) / sizeof(inter_buf[0]);
-    //
-    // size_t buf_size = sizeof(char) * 1024;
-    // size_t buf_len = 0;
-    // char *buf = (char *) malloc(buf_size);
-    // if (buf == nullptr) {
-    //     LOG_FATAL("Failed to allocate buf");
-    // }
-    //
-    // while (true) {
-    //     int received = recv(conn, inter_buf, inter_buf_size, 0);
-    //     for (int i = 0; i < received; i++) {
-    //         printf("'%c' ", inter_buf[i]);
-    //     }
-    //     putc('\n', stdout);
-    //     if (received == -1) {
-    //         LOG_FATALF("recv failed: %d %s", errno, strerror(errno));
-    //     } else if (received == 0) {
-    //         printf("Player disconnected\n");
-    //         break;
-    //     } else {
-    //         if (buf_len + received + 1 > buf_size) {
-    //             buf_size *= 2;
-    //             buf = realloc(buf, buf_size);
-    //             if (buf == nullptr) {
-    //                 LOG_FATAL("Failed to increase size of buf");
-    //             }
-    //         }
-    //
-    //         memcpy(buf + buf_len, inter_buf, received);
-    //         buf_len += received;
-    //         buf[buf_len] = '\0';
-    //
-    //         printf("buf: %s\n", buf);
-    //     }
-    //
-    //     // if (received == 0) {
-    //     //     printf("buf: %s\n", buf);
-    //     //     buf[0] = '\0';
-    //     //     break;
-    //     // } else if (received == -1) {
-    //     //     LOG_FATALF("recv failed: %d %s", errno, strerror(errno));
-    //     // } else {
-    //     //     buf[received] = '\0';
-    //     // }
-    //     // printf("loop buf: %s\n", buf);
-    // }
-
     return 0;
 }

+ 1 - 1
subprojects/scufeng/GIT_COMMIT

@@ -1 +1 @@
-e5666a0157e6e9b651b02e2cba571a980e71b5d2
+27f3dc720430939d045c6b38047960d2ce253c1a

+ 7 - 0
subprojects/scufeng/include/scufcont/DString.h

@@ -18,8 +18,10 @@ typedef struct {
 } SCDString;
 
 ScufContResult SCDString_alloc(SCDString *const dstr, const char *str, const size_t len);
+ScufContResult SCDString_alloc_copy(SCDString *const dstr, const SCDString *const o);
 ScufContResult SCDString_reserve(SCDString *const dstr, const size_t new_capacity);
 void SCDString_free(SCDString *const dstr);
+ScufContResult SCDString_copy(const SCDString *const src, SCDString *const dst);
 bool SCDString_equal(const SCDString *const s1, const SCDString *s2);
 bool SCDString_equal_c_str(const SCDString *const s1, const char *s2);
 bool SCDString_equal_next_chars_c_str(
@@ -28,6 +30,7 @@ bool SCDString_equal_next_chars_c_str(
     const size_t str_len,
     const size_t s
 );
+ScufContResult SCDString_concat_char(SCDString *const dstr, const char ch);
 ScufContResult SCDString_concat_c_str(
     SCDString *const dstr,
     const char *str,
@@ -47,5 +50,9 @@ ScufContResult SCDString_split(
     size_t *const arr_len
 );
 void SCDString_split_arr_free(SCDString **const arr, const size_t arr_len);
+ScufContResult SCDString_char_mod(SCDString *const dstr, const size_t at, const char ch);
+ScufContResult SCDString_char_remove_no_resize(SCDString *const dstr, const size_t at);
+ScufContResult SCDString_char_remove(SCDString *const dstr, const size_t at);
+ScufContResult SCDString_char_insert(SCDString *const dstr, const size_t at, const char ch);
 
 #endif // SCUFCONT_DSTRING_H_

+ 2 - 1
subprojects/scufeng/include/scufcont/scufcont.h

@@ -3,7 +3,8 @@
 
 #define SCUFCONT_RESULT_X(X) \
     X(SCUFCONT_SUCCESS) \
-    X(SCUFCONT_OUT_OF_MEMORY)
+    X(SCUFCONT_OUT_OF_MEMORY) \
+    X(SCUFCONT_OUT_OF_BOUNDS)
 
 #define X(val) val,
 

+ 2 - 18
subprojects/scufeng/include/scufeng/Shape.h → subprojects/scufeng/include/scufeng/Mesh.h

@@ -1,13 +1,11 @@
 #ifndef SCUFENG_SHAPE_H_
 #define SCUFENG_SHAPE_H_
 
-#include <stddef.h>
 #include <stdbool.h>
+#include <stddef.h>
 
 #include "cglm/types.h"
 
-#include "scufeng/engine.h"
-
 typedef enum {
     SE_TRIANGLES,
     SE_TRIANGLE_STRIP,
@@ -53,13 +51,6 @@ typedef struct {
     };
 } SEMesh;
 
-SEResult SEShape_alloc(
-    SETransform *const shape,
-    const SEVertex *const vertices,
-    const size_t vertices_n,
-    const vec4 color,
-    const SEDrawMode draw_mode
-);
 void SEMesh_free(SEMesh *const mesh);
 void SEMesh_set_draw_mode(SEMesh *mesh, const SEDrawMode mode);
 void SEMesh_render(const SEMesh *const mesh);
@@ -71,20 +62,13 @@ void SEMesh_bbox_get_2d(
     float *bottom
 );
 bool SEMesh_rect_is_pos_inside(const SEMesh *const mesh, const float x, const float y);
-void SEShape_rotation_set(SETransform *shape, vec3 rotation);
-void SEShape_rotate_2d_center(SETransform *const shape, const float w, const float h, const float angle_rad);
-void SEShape_scale_set(SETransform *shape, vec3 scale);
-void SEShape_scale(SETransform *const shape, vec3 scale);
-void SEShape_pos_set(SETransform *const shape, vec3 pos);
-void SEShape_translate_2d_center(SETransform *const shape, const float w, const float h, const vec2 pos);
-void SEShape_transform_reset(SETransform *shape);
-
 SEMesh SEMesh_rect_color_alloc(vec4 color);
 SEMesh SEMesh_string_alloc(
     const char *text,
     const float scale,
     const vec3 color
 );
+void SEMesh_render_multiple_2d(const SEMesh *const *const meshes, const size_t meshes_n);
 // void SEShape_transform_apply(SEShape *const shape);
 
 #endif // SCUFENG_SHAPE_H_

+ 0 - 12
subprojects/scufeng/include/scufeng/Scene.h

@@ -1,12 +0,0 @@
-#ifndef SCUFENG_SCENE_H_
-#define SCUFENG_SCENE_H_
-
-#include "scufeng/Shape.h"
-#include "engine.h"
-
-void se_render_meshes_2d(SEMesh *const *const meshes, const size_t meshes_n);
-SEResult se_render_line_segment(const vec2 p1, const vec2 p2, const vec4 color);
-SEResult se_render_line_segment_norm_coord(const vec2 p1, const vec2 p2, const vec4 color);
-void se_text_render(const char* text, int x, int y, float scale, const vec3 color);
-
-#endif // SCUFENG_SCENE_H_

+ 1 - 0
subprojects/scufeng/include/scufeng/engine.h

@@ -37,6 +37,7 @@ typedef enum {
 } SEDirection;
 
 typedef void (*SEKeyCb)(SEKey key, SEKeyAction action, SEKeyMod mods);
+typedef void (*SECharCb)(unsigned int codepoint);
 
 void se_init(const bool debug);
 void se_deinit(void);

+ 1 - 1
subprojects/scufeng/include/scufeng/scufcont_impl.h

@@ -2,7 +2,7 @@
 #define SE_SCUFCONT_IMPL_H_
 
 #include "scufcont/DArray.h"
-#include "scufeng/Shape.h"
+#include "scufeng/Mesh.h"
 
 SCDARRAY_HEADER(SETransform*, SEShapep)
 

+ 48 - 0
subprojects/scufeng/include/scufeng/text.h

@@ -1,12 +1,60 @@
 #ifndef SCUFENG_TEXT_H_
 #define SCUFENG_TEXT_H_
 
+#include "cglm/types.h"
+
 #include "scufeng/engine.h"
+#include "scufeng/Mesh.h"
+#include "scufcont/DString.h"
+
+typedef enum : uint8_t {
+    TI_FIXED_WIDTH = 1,
+} SETextInputOptions;
+
+typedef struct {
+    SETextInputOptions opts;
+    bool active;
+    size_t cursor;
+    SEMesh cursor_mesh;
+    SEMesh bg_mesh;
+    SCDString text;
+    int x;
+    int y;
+    float scale;
+    vec3 text_color;
+    float fixed_width;
+} SETextInput;
 
 SEResult se_font_load(
     const char *path,
     const unsigned int pixel_w,
     const unsigned int pixel_h
 );
+int se_text_render(
+    const char *text,
+    int x,
+    int y,
+    float scale,
+    const vec3 color
+);
+
+SETextInput SETextInput_alloc(
+    const char *text,
+    const size_t text_len,
+    const int x,
+    const int y,
+    vec3 color,
+    vec4 bg_color,
+    const SETextInputOptions opts
+);
+void SETextInput_free(SETextInput *const ti);
+void SETextInput_deactivate();
+void SETextInput_activate(SETextInput *const ti);
+SEResult SETextInput_char_insert_at_cursor(SETextInput *const ti, const char ch);
+void SETextInput_char_remove_at_cursor(SETextInput *const ti);
+bool SETextInput_is_pos_inside(const SETextInput *const ti, const float x, const float y);
+void SETextInput_fixed_width_set(SETextInput *const ti, const float w);
+void SETextInput_render_2d(SETextInput *const ti);
+void SETextInput_render_multiple_2d(SETextInput *const *const tis, const size_t ti_count);
 
 #endif // SCUFENG_TEXT_H_

BIN
subprojects/scufeng/libscufeng.a