[Refactor] One GameState to rule them all!
Introduces a central GameState class to manage the client's lifecycle, network connection and UI components. The Terminal has been demoted to a pure UI widget and main has been simplified to a basic entrypoint as they should be!
This commit is contained in:
		
							parent
							
								
									59783d2408
								
							
						
					
					
						commit
						00e78cc2ba
					
				
							
								
								
									
										81
									
								
								client/src/game_state.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								client/src/game_state.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
 | 
			
		||||
#include "game_state.h"
 | 
			
		||||
#include "gfx/shape_renderer.h"
 | 
			
		||||
#include "gfx/txt_renderer.h"
 | 
			
		||||
#include "terminal.h"
 | 
			
		||||
#include "ui/desktop.h"
 | 
			
		||||
#include "ui/ui_window.h"
 | 
			
		||||
 | 
			
		||||
GameState::GameState(void) = default;
 | 
			
		||||
 | 
			
		||||
GameState::~GameState(void) = default;
 | 
			
		||||
 | 
			
		||||
void GameState::init(void) {
 | 
			
		||||
  /* Create and connect the network client. */
 | 
			
		||||
  _network = std::make_unique<ClientNetwork>();
 | 
			
		||||
  if(_network->connect("127.0.0.1", 1337)) {
 | 
			
		||||
    /* TODO: handle connection success message. */
 | 
			
		||||
  } else {
 | 
			
		||||
    /* TODO: handle connection failure. */
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* Create the desktop. */
 | 
			
		||||
  _desktop = std::make_unique<Desktop>();
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
   * Create initial terminal window.
 | 
			
		||||
   */
 | 
			
		||||
  auto term = std::make_unique<Terminal>(_network.get()); /* Pass network connection. */
 | 
			
		||||
  auto term_window = std::make_unique<UIWindow>("Terminal", 100, 100, 800, 500);
 | 
			
		||||
  term_window->set_content(std::move(term));
 | 
			
		||||
  _desktop->add_window(std::move(term_window));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GameState::handle_event(SDL_Event* event) {
 | 
			
		||||
  if(_desktop) {
 | 
			
		||||
    _desktop->handle_event(event);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GameState::update(void) {
 | 
			
		||||
  std::string server_msg;
 | 
			
		||||
  while(_network->poll_message(server_msg)) {
 | 
			
		||||
    UIWindow* focused_window = _desktop->get_focused_window();
 | 
			
		||||
    if(!focused_window) continue;
 | 
			
		||||
 | 
			
		||||
    Terminal* terminal = focused_window->get_content();
 | 
			
		||||
    if(!terminal) continue;
 | 
			
		||||
 | 
			
		||||
    /* Server sends "output\nprompt", split them. */
 | 
			
		||||
    size_t last_newline = server_msg.find_last_of("\n");
 | 
			
		||||
    if(last_newline != std::string::npos) {
 | 
			
		||||
      std::string prompt = server_msg.substr(last_newline+1);
 | 
			
		||||
      terminal->set_prompt(prompt);
 | 
			
		||||
 | 
			
		||||
      std::string output = server_msg.substr(0, last_newline);
 | 
			
		||||
      if(!output.empty()) {
 | 
			
		||||
        /* split the multiline output and push each line to history. */
 | 
			
		||||
        std::stringstream ss(output);
 | 
			
		||||
        std::string line;
 | 
			
		||||
        while(std::getline(ss, line, '\n')) {
 | 
			
		||||
          terminal->add_history(line);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      terminal->add_history(server_msg);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if(_desktop) {
 | 
			
		||||
    _desktop->update();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GameState::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
 | 
			
		||||
                       int screen_height, bool show_cursor) {
 | 
			
		||||
  if(_desktop) {
 | 
			
		||||
    _desktop->render(shape_renderer, txt_renderer, screen_height, show_cursor);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								client/src/game_state.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								client/src/game_state.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
class ClientNetwork;
 | 
			
		||||
class Desktop;
 | 
			
		||||
class ShapeRenderer;
 | 
			
		||||
class TextRenderer;
 | 
			
		||||
union SDL_Event;
 | 
			
		||||
 | 
			
		||||
class GameState {
 | 
			
		||||
public:
 | 
			
		||||
  GameState(void);
 | 
			
		||||
  ~GameState(void);
 | 
			
		||||
 | 
			
		||||
  void init(void);
 | 
			
		||||
  void handle_event(SDL_Event* event);
 | 
			
		||||
  void update(void);
 | 
			
		||||
  void render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
 | 
			
		||||
              int screen_height, bool show_cursor);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  std::unique_ptr<ClientNetwork> _network;
 | 
			
		||||
  std::unique_ptr<Desktop>       _desktop;
 | 
			
		||||
};
 | 
			
		||||
@ -10,10 +10,9 @@
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include "../../server/src/network_manager.h"
 | 
			
		||||
 | 
			
		||||
#include "terminal.h"
 | 
			
		||||
#include "gfx/shape_renderer.h"
 | 
			
		||||
#include "ui/desktop.h"
 | 
			
		||||
#include "ui/ui_window.h"
 | 
			
		||||
#include "gfx/txt_renderer.h"
 | 
			
		||||
#include "game_state.h"
 | 
			
		||||
 | 
			
		||||
void run_server(void) {
 | 
			
		||||
  try {
 | 
			
		||||
@ -104,16 +103,8 @@ int main(int argc, char** argv) {
 | 
			
		||||
  /* Init shape renderer. */
 | 
			
		||||
  ShapeRenderer* shape_renderer_instance = new ShapeRenderer(SCREEN_WIDTH, SCREEN_HEIGHT);
 | 
			
		||||
 | 
			
		||||
  /* Create terminal. */
 | 
			
		||||
  auto term = std::make_unique<Terminal>();
 | 
			
		||||
 | 
			
		||||
  /* Create UI window and pass it a terminal. */
 | 
			
		||||
  auto test_window = std::make_unique<UIWindow>("Terminal", 100, 100, 800, 500);
 | 
			
		||||
  test_window->set_content(std::move(term));
 | 
			
		||||
 | 
			
		||||
  /* Create desktop and add the window. */
 | 
			
		||||
  auto desktop = std::make_unique<Desktop>();
 | 
			
		||||
  desktop->add_window(std::move(test_window));
 | 
			
		||||
  auto game_state = std::make_unique<GameState>();
 | 
			
		||||
  game_state->init();
 | 
			
		||||
 | 
			
		||||
  /* timer for cursor blink. */
 | 
			
		||||
  Uint32 last_blink_time = 0;
 | 
			
		||||
@ -125,10 +116,10 @@ int main(int argc, char** argv) {
 | 
			
		||||
    SDL_Event event;
 | 
			
		||||
    while(SDL_PollEvent(&event)) {
 | 
			
		||||
      if(event.type == SDL_EVENT_QUIT) { running = false; }
 | 
			
		||||
      desktop.get()->handle_event(&event);
 | 
			
		||||
      game_state->handle_event(&event);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    desktop.get()->update();
 | 
			
		||||
    game_state->update();
 | 
			
		||||
 | 
			
		||||
    Uint32 current_time = SDL_GetTicks();
 | 
			
		||||
    if(current_time - last_blink_time > 500) { /* Every 500ms. */
 | 
			
		||||
@ -140,14 +131,14 @@ int main(int argc, char** argv) {
 | 
			
		||||
    glClearColor(0.1f, 0.1f, 0.1, 1.0f);
 | 
			
		||||
    glClear(GL_COLOR_BUFFER_BIT);
 | 
			
		||||
 | 
			
		||||
    desktop.get()->render(shape_renderer_instance, txt_render_instance, SCREEN_HEIGHT, show_cursor);
 | 
			
		||||
    game_state->render(shape_renderer_instance, txt_render_instance, SCREEN_HEIGHT, show_cursor);
 | 
			
		||||
 | 
			
		||||
    /* It's really odd to call it SwapWindow now, rather than SwapBuffer. */
 | 
			
		||||
    SDL_GL_SwapWindow(window);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* Cleanup. */
 | 
			
		||||
  desktop.reset();
 | 
			
		||||
  game_state.reset();
 | 
			
		||||
  delete shape_renderer_instance;
 | 
			
		||||
  delete txt_render_instance;
 | 
			
		||||
  SDL_GL_DestroyContext(context);
 | 
			
		||||
 | 
			
		||||
@ -1,73 +1,44 @@
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <SDL3/SDL.h>
 | 
			
		||||
#include <GL/glew.h>
 | 
			
		||||
#include <SDL3/SDL_events.h>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
 | 
			
		||||
#include "terminal.h"
 | 
			
		||||
#include "client_network.h"
 | 
			
		||||
#include "gfx/txt_renderer.h"
 | 
			
		||||
 | 
			
		||||
Terminal::Terminal(void) {
 | 
			
		||||
Terminal::Terminal(ClientNetwork* network) : _network(network) {
 | 
			
		||||
  /* Placeholder welcome message to history. */
 | 
			
		||||
  _history.push_back("Welcome to Bettola");
 | 
			
		||||
  _history.push_back("Connecting to server...");
 | 
			
		||||
 | 
			
		||||
  _network        = std::make_unique<ClientNetwork>();
 | 
			
		||||
  _should_close   = false;
 | 
			
		||||
  _scroll_offset  = 0;
 | 
			
		||||
  _prompt         = "";
 | 
			
		||||
 | 
			
		||||
  /* TODO: Move network to main.cpp, or better yet, a dedicatated game state file */
 | 
			
		||||
  if(_network->connect("127.0.0.1", 1337)) {
 | 
			
		||||
    _history.push_back("Connection successful.");
 | 
			
		||||
  } else {
 | 
			
		||||
    _history.push_back("Connection failed. Please restart.");
 | 
			
		||||
    _prompt = "error>";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Terminal::~Terminal(void) {}
 | 
			
		||||
 | 
			
		||||
void Terminal::update(void) {
 | 
			
		||||
  std::string server_msg;
 | 
			
		||||
  while(_network->poll_message(server_msg)) {
 | 
			
		||||
    if(server_msg == "__CLOSE_CONNECTION__") {
 | 
			
		||||
      _history.push_back("Connection closed by server.");
 | 
			
		||||
      _should_close = true;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    /* Server will send "output\nprompt". Split them. */
 | 
			
		||||
    size_t last_newline = server_msg.find_last_of('\n');
 | 
			
		||||
    if(last_newline != std::string::npos) {
 | 
			
		||||
      _prompt = server_msg.substr(last_newline+1);
 | 
			
		||||
      std::string output = server_msg.substr(0, last_newline);
 | 
			
		||||
      if(!output.empty()) {
 | 
			
		||||
        /* Split multiline output and push each line to history. */
 | 
			
		||||
        std::stringstream ss(output);
 | 
			
		||||
        std::string line;
 | 
			
		||||
        while(std::getline(ss, line, '\n')) {
 | 
			
		||||
          /* Replace tabs. */
 | 
			
		||||
          std::string line_with_spaces;
 | 
			
		||||
          for(char ch : line) {
 | 
			
		||||
            if(ch == '\t') {
 | 
			
		||||
              line_with_spaces += "   ";
 | 
			
		||||
            } else {
 | 
			
		||||
              line_with_spaces += ch;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          _history.push_back(line_with_spaces);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Terminal::add_history(const std::string& line) {
 | 
			
		||||
  std::string line_with_spaces;
 | 
			
		||||
  for(char ch : line) {
 | 
			
		||||
    if(ch == '\t') {
 | 
			
		||||
      line_with_spaces += "   ";
 | 
			
		||||
    } else {
 | 
			
		||||
      /*
 | 
			
		||||
       * If there's no newline, it might be the initial welcome message,
 | 
			
		||||
       * or some other non-standard message, just it to history.
 | 
			
		||||
       */
 | 
			
		||||
      _history.push_back(server_msg);
 | 
			
		||||
      line_with_spaces += ch;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  _history.push_back(line_with_spaces);
 | 
			
		||||
 | 
			
		||||
  if(line == "__CLOSE_CONNECTION__") {
 | 
			
		||||
    _history.back() = "Connection closed by server.";
 | 
			
		||||
    _should_close = true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Terminal::set_prompt(const std::string& prompt) {
 | 
			
		||||
  _prompt = prompt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Terminal::close(void) { return _should_close; }
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <SDL3/SDL.h>
 | 
			
		||||
@ -9,13 +8,15 @@
 | 
			
		||||
 | 
			
		||||
class Terminal {
 | 
			
		||||
public:
 | 
			
		||||
  Terminal(void);
 | 
			
		||||
  Terminal(ClientNetwork* network);
 | 
			
		||||
  ~Terminal(void);
 | 
			
		||||
 | 
			
		||||
  void update(void);
 | 
			
		||||
  void handle_input(SDL_Event* event);
 | 
			
		||||
  void render(TextRenderer* renderer, int x, int y, int width, int height, bool show_cursor);
 | 
			
		||||
  void scroll(int amount, int content_height);
 | 
			
		||||
  void add_history(const std::string& line);
 | 
			
		||||
  void set_prompt(const std::string& prompt);
 | 
			
		||||
  bool close(void);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
@ -26,5 +27,5 @@ private:
 | 
			
		||||
  std::vector<std::string> _history;
 | 
			
		||||
  int _scroll_offset;
 | 
			
		||||
  std::string _prompt;
 | 
			
		||||
  std::unique_ptr<ClientNetwork> _network;
 | 
			
		||||
  ClientNetwork* _network;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -55,14 +55,7 @@ void Desktop::handle_event(SDL_Event* event) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Desktop::update(void) {
 | 
			
		||||
  /* Poll all windows for network updates. */
 | 
			
		||||
  for(auto& window : _windows) {
 | 
			
		||||
    Terminal* term = window->get_content();
 | 
			
		||||
    if(term) {
 | 
			
		||||
      term->update();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* Remove closed windows. */
 | 
			
		||||
  _windows.erase(std::remove_if(_windows.begin(), _windows.end(),
 | 
			
		||||
                                [](const std::unique_ptr<UIWindow>& window) {
 | 
			
		||||
                                  Terminal* term = window->get_content();
 | 
			
		||||
@ -71,6 +64,10 @@ void Desktop::update(void) {
 | 
			
		||||
                              _windows.end());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
UIWindow* Desktop::get_focused_window(void) {
 | 
			
		||||
  return _focused_window;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Desktop::render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer,
 | 
			
		||||
                     int screen_height, bool show_cursor) {
 | 
			
		||||
  for(const auto& win : _windows) {
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,8 @@ public:
 | 
			
		||||
  void render(ShapeRenderer* shape_renderer, TextRenderer* txt_renderer, int screen_height,
 | 
			
		||||
              bool show_cursor);
 | 
			
		||||
 | 
			
		||||
  UIWindow* get_focused_window(void);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  std::vector<std::unique_ptr<UIWindow>> _windows;
 | 
			
		||||
  UIWindow* _focused_window;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user