Non-blocking map loading

This commit is contained in:
tovjemam 2026-03-21 14:16:03 +01:00
parent 773d80109a
commit d0b30ed56b
13 changed files with 325 additions and 117 deletions

View File

@ -1,12 +1,13 @@
#include "cmdfile.hpp" #include "cmdfile.hpp"
void assets::LoadCMDFile(const std::string& filename, void assets::LoadCMDStream(std::istream& is, CmdCallback handler)
const std::function<void(const std::string& command, std::istringstream& iss)>& handler)
{ {
std::istringstream file = fs::ReadFileAsStream(filename); std::string line, command;
std::string line; if (is.eof())
while (std::getline(file, line)) return;
while (std::getline(is, line))
{ {
if (line.empty() || line[0] == '#') // Skip empty lines and comments if (line.empty() || line[0] == '#') // Skip empty lines and comments
continue; continue;
@ -15,13 +16,20 @@ void assets::LoadCMDFile(const std::string& filename,
line.erase(0, line.find_first_not_of(" \t")); line.erase(0, line.find_first_not_of(" \t"));
std::istringstream iss(line); std::istringstream iss(line);
std::string command;
iss >> command; iss >> command;
handler(command, iss); if (!handler(command, iss))
return;
}
} }
void assets::LoadCMDFile(const std::string& filename, CmdCallbackVoid handler)
{
std::istringstream file = fs::ReadFileAsStream(filename);
LoadCMDStream(file, [handler](const std::string& command, std::istringstream& iss) {
handler(command, iss);
return true;
});
} }
std::string assets::ParseString(std::istringstream& iss) std::string assets::ParseString(std::istringstream& iss)

View File

@ -10,9 +10,11 @@
namespace assets namespace assets
{ {
void LoadCMDFile(const std::string& filename, using CmdCallback = std::function<bool(const std::string& command, std::istringstream& iss)>;
const std::function<void(const std::string& command, std::istringstream& iss)>& handler); using CmdCallbackVoid = std::function<void(const std::string& command, std::istringstream& iss)>;
void LoadCMDStream(std::istream& is, CmdCallback handler);
void LoadCMDFile(const std::string& filename, CmdCallbackVoid handler);
inline void ParseTransform(std::istringstream& iss, Transform& trans) inline void ParseTransform(std::istringstream& iss, Transform& trans)
{ {

View File

@ -28,8 +28,131 @@ static AABB3 TransformAABB(const AABB3& aabb, const glm::mat4& mat)
std::shared_ptr<const assets::Map> assets::Map::LoadFromFile(const std::string& filename) std::shared_ptr<const assets::Map> assets::Map::LoadFromFile(const std::string& filename)
{ {
auto map = std::make_shared<Map>(); MapLoader loader(filename);
while (loader.Next()) {}
return loader.GetMap();
}
const assets::MapGraph* assets::Map::GetGraph(const std::string& name) const
{
auto it = graphs_.find(name);
if (it != graphs_.end())
return &it->second;
return nullptr;
}
// MapLoader
assets::MapLoader::MapLoader(const std::string& filename)
: map_iss_(fs::ReadFileAsStream(filename)), map_(std::make_shared<Map>())
{
}
bool assets::MapLoader::Next()
{
switch (state_)
{
case ML_INIT:
state_ = ML_READ_MODELS;
return true;
case ML_READ_MODELS:
ReadModels();
state_ = ML_LOAD_BASEMODEL;
return true;
case ML_LOAD_BASEMODEL:
LoadBaseModel();
state_ = ML_LOAD_MODELS;
return true;
case ML_LOAD_MODELS:
if (LoadNextModel())
return true;
state_ = ML_STRUCTS;
return true;
case ML_STRUCTS:
LoadStructs();
state_ = ML_FINISHED;
return true;
default:
return false;
}
}
int assets::MapLoader::GetPercent() const
{
switch (state_)
{
case ML_INIT:
case ML_READ_MODELS:
return 0;
case ML_LOAD_BASEMODEL:
return 10;
case ML_LOAD_MODELS:
return 60 + models_.size() * 30 / model_names_.size();
case ML_STRUCTS:
return 90;
default:
return 100;
}
}
std::shared_ptr<const assets::Map> assets::MapLoader::GetMap() const
{
if (state_ != ML_FINISHED)
return nullptr;
return map_;
}
void assets::MapLoader::ReadModels()
{
LoadCMDStream(map_iss_, [&](const std::string& command, std::istringstream& iss) {
if (command == "basemodel")
{
iss >> basemodel_name_;
}
else if (command == "model")
{
std::string model_name;
iss >> model_name;
model_names_.emplace_back(std::move(model_name));
}
else if (command == "endmodels")
{
return false;
}
return true;
});
}
void assets::MapLoader::LoadBaseModel()
{
map_->basemodel_ = CacheManager::GetModel("data/" + basemodel_name_ + ".mdl");
}
bool assets::MapLoader::LoadNextModel()
{
if (models_.size() >= model_names_.size())
return false;
const auto& model_name = model_names_[models_.size()];
models_.push_back(assets::CacheManager::GetModel("data/" + model_name + ".mdl"));
return true;
}
void assets::MapLoader::LoadStructs()
{
MapGraph* graph = nullptr; MapGraph* graph = nullptr;
std::vector<std::tuple<size_t, size_t>> graph_edges; std::vector<std::tuple<size_t, size_t>> graph_edges;
@ -49,24 +172,20 @@ std::shared_ptr<const assets::Map> assets::Map::LoadFromFile(const std::string&
Chunk* chunk = nullptr; Chunk* chunk = nullptr;
LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) { LoadCMDStream(map_iss_, [&](const std::string& command, std::istringstream& iss) {
if (command == "basemodel") if (command == "static")
{
std::string model_name;
iss >> model_name;
map->basemodel_ = CacheManager::GetModel("data/" + model_name + ".mdl");
}
else if (command == "static")
{ {
if (!chunk) if (!chunk)
throw std::runtime_error("static in map without chunk"); throw std::runtime_error("static in map without chunk");
MapStaticObject obj; MapStaticObject obj;
std::string model_name; size_t model_idx;
iss >> model_name; iss >> model_idx;
obj.model = assets::CacheManager::GetModel("data/" + model_name + ".mdl"); if (model_idx >= models_.size())
throw std::runtime_error("static in map with out of range model idx");
obj.model = models_[model_idx];
glm::vec3 angles; glm::vec3 angles;
@ -87,18 +206,18 @@ std::shared_ptr<const assets::Map> assets::Map::LoadFromFile(const std::string&
} }
} }
map->objs_.push_back(std::move(obj)); map_->objs_.push_back(std::move(obj));
chunk->num_objs++; chunk->num_objs++;
} }
else if (command == "chunk") else if (command == "chunk")
{ {
glm::ivec2 coord; glm::ivec2 coord;
chunk = &map->chunks_.emplace_back(); chunk = &map_->chunks_.emplace_back();
iss >> coord.x >> coord.y; iss >> coord.x >> coord.y;
iss >> chunk->aabb.min.x >> chunk->aabb.min.y >> chunk->aabb.min.z; iss >> chunk->aabb.min.x >> chunk->aabb.min.y >> chunk->aabb.min.z;
iss >> chunk->aabb.max.x >> chunk->aabb.max.y >> chunk->aabb.max.z; iss >> chunk->aabb.max.x >> chunk->aabb.max.y >> chunk->aabb.max.z;
chunk->first_obj = map->objs_.size(); chunk->first_obj = map_->objs_.size();
} }
else if (command == "surface") else if (command == "surface")
{ {
@ -110,10 +229,10 @@ std::shared_ptr<const assets::Map> assets::Map::LoadFromFile(const std::string&
throw std::runtime_error("surface in map without chunk"); throw std::runtime_error("surface in map without chunk");
#ifdef CLIENT #ifdef CLIENT
if (!map->basemodel_) if (!map_->basemodel_)
throw std::runtime_error("surface in map with no basemodel"); throw std::runtime_error("surface in map with no basemodel");
auto mesh = map->basemodel_->GetMesh(); auto mesh = map_->basemodel_->GetMesh();
if (!mesh) if (!mesh)
throw std::runtime_error("surface in map with no basemodel mesh"); throw std::runtime_error("surface in map with no basemodel mesh");
@ -138,7 +257,7 @@ std::shared_ptr<const assets::Map> assets::Map::LoadFromFile(const std::string&
std::string graph_name; std::string graph_name;
iss >> graph_name; iss >> graph_name;
graph = &map->graphs_[graph_name]; graph = &map_->graphs_[graph_name];
graph_edges.clear(); graph_edges.clear();
} }
else if (command == "n") else if (command == "n")
@ -162,18 +281,11 @@ std::shared_ptr<const assets::Map> assets::Map::LoadFromFile(const std::string&
graph_edges.emplace_back(from_idx, to_idx); graph_edges.emplace_back(from_idx, to_idx);
} }
return true;
}); });
if (graph) if (graph)
ProcessGraph(); ProcessGraph();
return map;
}
const assets::MapGraph* assets::Map::GetGraph(const std::string& name) const
{
auto it = graphs_.find(name);
if (it != graphs_.end())
return &it->second;
return nullptr;
} }

View File

@ -4,6 +4,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <sstream>
#include "game/transform_node.hpp" #include "game/transform_node.hpp"
#include "model.hpp" #include "model.hpp"
@ -66,6 +67,46 @@ private:
std::vector<Chunk> chunks_; std::vector<Chunk> chunks_;
std::vector<MapStaticObject> objs_; std::vector<MapStaticObject> objs_;
std::map<std::string, MapGraph> graphs_; std::map<std::string, MapGraph> graphs_;
friend class MapLoader;
};
enum MapLoadingState
{
ML_INIT,
ML_READ_MODELS,
ML_LOAD_BASEMODEL,
ML_LOAD_MODELS,
ML_STRUCTS,
ML_FINISHED,
};
class MapLoader
{
public:
MapLoader(const std::string& filename);
bool Next();
int GetPercent() const;
std::shared_ptr<const Map> GetMap() const;
private:
void ReadModels();
void LoadBaseModel();
bool LoadNextModel();
void LoadStructs();
private:
std::istringstream map_iss_;
MapLoadingState state_ = ML_INIT;
std::string basemodel_name_;
std::vector<std::string> model_names_;
std::vector<std::shared_ptr<const Model>> models_;
std::shared_ptr<Map> map_;
}; };
} // namespace assets } // namespace assets

View File

@ -38,16 +38,12 @@ void App::Frame()
session_->Update(updinfo); session_->Update(updinfo);
} }
gfx::DrawListParams params{};
renderer_.Begin(viewport_size_.x, viewport_size_.y);
renderer_.ClearColor(glm::vec3(0.5f, 0.7f, 1.0f));
renderer_.ClearDepth();
dlist_.Clear();
gfx::DrawListParams params;
params.screen_width = viewport_size_.x; params.screen_width = viewport_size_.x;
params.screen_height = viewport_size_.y; params.screen_height = viewport_size_.y;
params.env.clear_color = glm::vec3(0.1f);
dlist_.Clear();
gui_.Begin(); gui_.Begin();
// draw session // draw session

View File

@ -182,7 +182,7 @@ void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListPar
// glm::mat4 fake_view_proj = glm::perspective(glm::radians(30.0f), aspect, 0.1f, 3000.0f) * view; // glm::mat4 fake_view_proj = glm::perspective(glm::radians(30.0f), aspect, 0.1f, 3000.0f) * view;
game::view::DrawArgs draw_args(dlist, gui, params.view_proj, eye, glm::ivec2(params.screen_width, params.screen_height), 500.0f); game::view::DrawArgs draw_args(dlist, params.env, gui, params.view_proj, eye, glm::ivec2(params.screen_width, params.screen_height), 500.0f);
world_->Draw(draw_args); world_->Draw(draw_args);
glm::mat4 camera_world = glm::inverse(view); glm::mat4 camera_world = glm::inverse(view);

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "gfx/draw_list.hpp" #include "gfx/draw_list.hpp"
#include "gfx/renderer.hpp"
#include "gfx/frustum.hpp" #include "gfx/frustum.hpp"
#include "gui/context.hpp" #include "gui/context.hpp"
@ -10,6 +11,7 @@ namespace game::view
struct DrawArgs struct DrawArgs
{ {
gfx::DrawList& dlist; gfx::DrawList& dlist;
gfx::DrawListEnvironmentParams& env;
gui::Context& gui; gui::Context& gui;
const glm::mat4 view_proj; const glm::mat4 view_proj;
@ -18,8 +20,10 @@ struct DrawArgs
const glm::ivec2 screen_size; const glm::ivec2 screen_size;
const float render_distance; const float render_distance;
DrawArgs(gfx::DrawList& dlist, gui::Context& gui, const glm::mat4& view_proj, const glm::vec3& eye, const glm::ivec2& screen_size, float render_distance) DrawArgs(gfx::DrawList& dlist, gfx::DrawListEnvironmentParams& env, gui::Context& gui, const glm::mat4& view_proj,
: dlist(dlist), gui(gui), view_proj(view_proj), eye(eye), frustum(view_proj), screen_size(screen_size), render_distance(render_distance) const glm::vec3& eye, const glm::ivec2& screen_size, float render_distance)
: dlist(dlist), env(env), gui(gui), view_proj(view_proj), eye(eye), frustum(view_proj),
screen_size(screen_size), render_distance(render_distance)
{ {
} }
}; };

View File

@ -6,13 +6,36 @@
game::view::MapInstanceView::MapInstanceView(const std::string& map_name) game::view::MapInstanceView::MapInstanceView(const std::string& map_name)
{ {
map_ = assets::CacheManager::GetMap("data/" + map_name + ".map"); loader_ = std::make_unique<assets::MapLoader>("data/" + map_name + ".map");
}
void game::view::MapInstanceView::LoadNext()
{
if (IsLoaded())
return;
if (loader_->Next())
return;
// just loaded
map_ = loader_->GetMap();
objs_visible_.resize(map_->GetStaticObjects().size(), true); objs_visible_.resize(map_->GetStaticObjects().size(), true);
loader_.reset();
}
int game::view::MapInstanceView::GetLoadingPercent() const
{
if (!loader_)
return 100;
return loader_->GetPercent();
} }
void game::view::MapInstanceView::Draw(const game::view::DrawArgs& args) const void game::view::MapInstanceView::Draw(const game::view::DrawArgs& args) const
{ {
if (!map_)
return;
const auto& basemodel = map_->GetBaseModel(); const auto& basemodel = map_->GetBaseModel();
if (!basemodel || !basemodel->GetMesh()) if (!basemodel || !basemodel->GetMesh())
@ -40,8 +63,10 @@ void game::view::MapInstanceView::Draw(const game::view::DrawArgs& args) const
void game::view::MapInstanceView::EnableObj(net::ObjNum num, bool enable) void game::view::MapInstanceView::EnableObj(net::ObjNum num, bool enable)
{ {
size_t i = static_cast<size_t>(num); size_t i = static_cast<size_t>(num);
// map may be not loaded yet, in that case make the visible flag fit
if (i >= objs_visible_.size()) if (i >= objs_visible_.size())
return; objs_visible_.resize(i + 1, true);
objs_visible_[i] = enable; objs_visible_[i] = enable;
} }

View File

@ -12,6 +12,10 @@ class MapInstanceView
public: public:
MapInstanceView(const std::string& map_name); MapInstanceView(const std::string& map_name);
void LoadNext();
bool IsLoaded() const { return loader_.get() == nullptr; }
int GetLoadingPercent() const;
void Draw(const game::view::DrawArgs& args) const; void Draw(const game::view::DrawArgs& args) const;
void EnableObj(net::ObjNum num, bool enable); void EnableObj(net::ObjNum num, bool enable);
@ -20,6 +24,7 @@ private:
void DrawChunk(const game::view::DrawArgs& args, const assets::Mesh& basemesh, const assets::Chunk& chunk) const; void DrawChunk(const game::view::DrawArgs& args, const assets::Mesh& basemesh, const assets::Chunk& chunk) const;
private: private:
std::unique_ptr<assets::MapLoader> loader_;
std::shared_ptr<const assets::Map> map_; std::shared_ptr<const assets::Map> map_;
std::vector<bool> objs_visible_; std::vector<bool> objs_visible_;

View File

@ -6,6 +6,7 @@
#include "characterview.hpp" #include "characterview.hpp"
#include "vehicleview.hpp" #include "vehicleview.hpp"
#include "client_session.hpp" #include "client_session.hpp"
#include "draw_args.hpp"
game::view::WorldView::WorldView(ClientSession& session, net::InMessage& msg) : game::view::WorldView::WorldView(ClientSession& session, net::InMessage& msg) :
session_(session), audiomaster_(session_.GetAudioMaster()), map_("openworld") session_(session), audiomaster_(session_.GetAudioMaster()), map_("openworld")
@ -68,6 +69,9 @@ void game::view::WorldView::Update(const UpdateInfo& info)
{ {
time_ = info.time; time_ = info.time;
if (!map_.IsLoaded())
map_.LoadNext();
for (const auto& [entnum, ent] : ents_) for (const auto& [entnum, ent] : ents_)
{ {
ent->TryUpdate(info); ent->TryUpdate(info);
@ -76,6 +80,14 @@ void game::view::WorldView::Update(const UpdateInfo& info)
void game::view::WorldView::Draw(const DrawArgs& args) const void game::view::WorldView::Draw(const DrawArgs& args) const
{ {
if (!map_.IsLoaded())
{
DrawLoadingScreen(args);
return;
}
args.env.clear_color = glm::vec3(0.5f, 0.7f, 1.0f);
map_.Draw(args); map_.Draw(args);
for (const auto& [entnum, ent] : ents_) for (const auto& [entnum, ent] : ents_)
@ -119,6 +131,22 @@ game::view::EntityView* game::view::WorldView::GetEntity(net::EntNum entnum)
return nullptr; return nullptr;
} }
void game::view::WorldView::DrawLoadingScreen(const DrawArgs& args) const
{
float margin = 50.0f;
glm::vec2 size(400.0f, 15.0f);
glm::vec2 pos(margin, args.screen_size.y - margin - size.y);
int loaded_percent = map_.GetLoadingPercent();
float loaded = static_cast<float>(loaded_percent) * 0.01f;
args.gui.DrawRect(pos, pos + size, 0x77FFFFFF);
args.gui.DrawRect(pos, pos + glm::vec2(size.x * loaded, size.y), 0xFF00FFFF);
std::string load_text = std::to_string(loaded_percent) + "%";
args.gui.DrawTextAligned(load_text, pos + glm::vec2(size.x + 50.0f, size.y * 0.5f), glm::vec2(-0.5f, -0.5f));
}
bool game::view::WorldView::ProcessEntSpawnMsg(net::InMessage& msg) bool game::view::WorldView::ProcessEntSpawnMsg(net::InMessage& msg)
{ {
net::EntNum entnum; net::EntNum entnum;

View File

@ -33,6 +33,8 @@ public:
audio::Master& GetAudioMaster() const { return audiomaster_; } audio::Master& GetAudioMaster() const { return audiomaster_; }
private: private:
void DrawLoadingScreen(const DrawArgs& args) const;
// msg handlers // msg handlers
bool ProcessEntSpawnMsg(net::InMessage& msg); bool ProcessEntSpawnMsg(net::InMessage& msg);
bool ProcessEntMsgMsg(net::InMessage& msg); bool ProcessEntMsgMsg(net::InMessage& msg);

View File

@ -24,27 +24,13 @@ gfx::Renderer::Renderer()
SetupBeamVA(); SetupBeamVA();
} }
void gfx::Renderer::Begin(size_t width, size_t height)
{
current_shader_ = nullptr;
glViewport(0, 0, width, height);
//glEnable(GL_MULTISAMPLE);
}
void gfx::Renderer::ClearColor(const glm::vec3& color)
{
glClearColor(color.r, color.g, color.b, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
void gfx::Renderer::ClearDepth()
{
glClear(GL_DEPTH_BUFFER_BIT);
}
void gfx::Renderer::DrawList(gfx::DrawList& list, const DrawListParams& params) void gfx::Renderer::DrawList(gfx::DrawList& list, const DrawListParams& params)
{ {
current_shader_ = nullptr;
glViewport(0, 0, params.screen_width, params.screen_height);
glClearColor(params.env.clear_color.r, params.env.clear_color.g, params.env.clear_color.b, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
DrawSurfaceList(list.surfaces, params); DrawSurfaceList(list.surfaces, params);
DrawBeamList(list.beams, params); DrawBeamList(list.beams, params);
DrawHudList(list.huds, params); DrawHudList(list.huds, params);

View File

@ -3,13 +3,20 @@
#include <memory> #include <memory>
#include <span> #include <span>
#include "shader.hpp"
#include "draw_list.hpp" #include "draw_list.hpp"
#include "shader.hpp"
namespace gfx namespace gfx
{ {
struct DrawListEnvironmentParams
{
glm::vec3 clear_color;
};
struct DrawListParams struct DrawListParams
{ {
DrawListEnvironmentParams env;
glm::vec3 cam_pos; glm::vec3 cam_pos;
glm::mat4 view_proj; glm::mat4 view_proj;
size_t screen_width = 0; size_t screen_width = 0;
@ -30,12 +37,6 @@ namespace gfx
{ {
public: public:
Renderer(); Renderer();
void Begin(size_t width, size_t height);
void ClearColor(const glm::vec3& color);
void ClearDepth();
void DrawList(gfx::DrawList& list, const DrawListParams& params); void DrawList(gfx::DrawList& list, const DrawListParams& params);
private: private:
@ -62,8 +63,6 @@ namespace gfx
std::unique_ptr<Shader> hud_shader_; std::unique_ptr<Shader> hud_shader_;
const Shader* current_shader_ = nullptr; const Shader* current_shader_ = nullptr;
}; };
} } // namespace gfx