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

View File

@ -10,9 +10,11 @@
namespace assets
{
void LoadCMDFile(const std::string& filename,
const std::function<void(const std::string& command, std::istringstream& iss)>& handler);
using CmdCallback = std::function<bool(const std::string& command, std::istringstream& iss)>;
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)
{

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)
{
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;
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;
LoadCMDFile(filename, [&](const std::string& command, std::istringstream& iss) {
if (command == "basemodel")
{
std::string model_name;
iss >> model_name;
map->basemodel_ = CacheManager::GetModel("data/" + model_name + ".mdl");
}
else if (command == "static")
LoadCMDStream(map_iss_, [&](const std::string& command, std::istringstream& iss) {
if (command == "static")
{
if (!chunk)
throw std::runtime_error("static in map without chunk");
MapStaticObject obj;
std::string model_name;
iss >> model_name;
size_t model_idx;
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;
@ -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++;
}
else if (command == "chunk")
{
glm::ivec2 coord;
chunk = &map->chunks_.emplace_back();
chunk = &map_->chunks_.emplace_back();
iss >> coord.x >> coord.y;
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;
chunk->first_obj = map->objs_.size();
chunk->first_obj = map_->objs_.size();
}
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");
#ifdef CLIENT
if (!map->basemodel_)
if (!map_->basemodel_)
throw std::runtime_error("surface in map with no basemodel");
auto mesh = map->basemodel_->GetMesh();
auto mesh = map_->basemodel_->GetMesh();
if (!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;
iss >> graph_name;
graph = &map->graphs_[graph_name];
graph = &map_->graphs_[graph_name];
graph_edges.clear();
}
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);
}
return true;
});
if (graph)
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 <string>
#include <vector>
#include <sstream>
#include "game/transform_node.hpp"
#include "model.hpp"
@ -66,6 +67,46 @@ private:
std::vector<Chunk> chunks_;
std::vector<MapStaticObject> objs_;
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

View File

@ -38,16 +38,12 @@ void App::Frame()
session_->Update(updinfo);
}
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;
gfx::DrawListParams params{};
params.screen_width = viewport_size_.x;
params.screen_height = viewport_size_.y;
params.env.clear_color = glm::vec3(0.1f);
dlist_.Clear();
gui_.Begin();
// 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;
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);
glm::mat4 camera_world = glm::inverse(view);

View File

@ -1,6 +1,7 @@
#pragma once
#include "gfx/draw_list.hpp"
#include "gfx/renderer.hpp"
#include "gfx/frustum.hpp"
#include "gui/context.hpp"
@ -10,16 +11,19 @@ namespace game::view
struct DrawArgs
{
gfx::DrawList& dlist;
gfx::DrawListEnvironmentParams& env;
gui::Context& gui;
const glm::mat4 view_proj;
const glm::vec3 eye;
const gfx::Frustum frustum;
const glm::ivec2 screen_size;
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)
: dlist(dlist), gui(gui), view_proj(view_proj), eye(eye), frustum(view_proj), screen_size(screen_size), render_distance(render_distance)
DrawArgs(gfx::DrawList& dlist, gfx::DrawListEnvironmentParams& env, gui::Context& gui, const glm::mat4& view_proj,
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)
{
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);
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
{
if (!map_)
return;
const auto& basemodel = map_->GetBaseModel();
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)
{
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())
return;
objs_visible_.resize(i + 1, true);
objs_visible_[i] = enable;
}

View File

@ -12,6 +12,10 @@ class MapInstanceView
public:
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 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;
private:
std::unique_ptr<assets::MapLoader> loader_;
std::shared_ptr<const assets::Map> map_;
std::vector<bool> objs_visible_;

View File

@ -6,6 +6,7 @@
#include "characterview.hpp"
#include "vehicleview.hpp"
#include "client_session.hpp"
#include "draw_args.hpp"
game::view::WorldView::WorldView(ClientSession& session, net::InMessage& msg) :
session_(session), audiomaster_(session_.GetAudioMaster()), map_("openworld")
@ -68,6 +69,9 @@ void game::view::WorldView::Update(const UpdateInfo& info)
{
time_ = info.time;
if (!map_.IsLoaded())
map_.LoadNext();
for (const auto& [entnum, ent] : ents_)
{
ent->TryUpdate(info);
@ -76,6 +80,14 @@ void game::view::WorldView::Update(const UpdateInfo& info)
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);
for (const auto& [entnum, ent] : ents_)
@ -119,6 +131,22 @@ game::view::EntityView* game::view::WorldView::GetEntity(net::EntNum entnum)
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)
{
net::EntNum entnum;

View File

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

View File

@ -24,27 +24,13 @@ gfx::Renderer::Renderer()
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)
{
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);
DrawBeamList(list.beams, params);
DrawHudList(list.huds, params);

View File

@ -3,67 +3,66 @@
#include <memory>
#include <span>
#include "shader.hpp"
#include "draw_list.hpp"
#include "shader.hpp"
namespace gfx
{
struct DrawListParams
{
glm::vec3 cam_pos;
glm::mat4 view_proj;
size_t screen_width = 0;
size_t screen_height = 0;
};
struct MeshShader
{
std::unique_ptr<Shader> shader;
struct DrawListEnvironmentParams
{
glm::vec3 clear_color;
};
// cached state to avoid redundant uniform updates which are expensive especially on WebGL
bool global_setup = false;
glm::vec4 color = glm::vec4(-1.0f); // invalid to force initial setup
int flags = 0;
};
struct DrawListParams
{
DrawListEnvironmentParams env;
glm::vec3 cam_pos;
glm::mat4 view_proj;
size_t screen_width = 0;
size_t screen_height = 0;
};
class Renderer
{
public:
Renderer();
struct MeshShader
{
std::unique_ptr<Shader> shader;
void Begin(size_t width, size_t height);
// cached state to avoid redundant uniform updates which are expensive especially on WebGL
bool global_setup = false;
glm::vec4 color = glm::vec4(-1.0f); // invalid to force initial setup
int flags = 0;
};
void ClearColor(const glm::vec3& color);
void ClearDepth();
class Renderer
{
public:
Renderer();
void DrawList(gfx::DrawList& list, const DrawListParams& params);
void DrawList(gfx::DrawList& list, const DrawListParams& params);
private:
void SetupBeamVA();
private:
void SetupBeamVA();
void InvalidateShaders();
void InvalidateMeshShader(MeshShader& mshader);
void SetupMeshShader(MeshShader& mshader, const DrawListParams& params);
void InvalidateShaders();
void InvalidateMeshShader(MeshShader& mshader);
void SetupMeshShader(MeshShader& mshader, const DrawListParams& params);
void DrawSurfaceList(std::span<DrawSurfaceCmd> queue, const DrawListParams& params);
void DrawBeamList(std::span<DrawBeamCmd> queue, const DrawListParams& params);
void DrawHudList(std::span<DrawHudCmd> queue, const DrawListParams& params);
void DrawSurfaceList(std::span<DrawSurfaceCmd> queue, const DrawListParams& params);
void DrawBeamList(std::span<DrawBeamCmd> queue, const DrawListParams& params);
void DrawHudList(std::span<DrawHudCmd> queue, const DrawListParams& params);
private:
MeshShader mesh_shader_;
MeshShader skel_mesh_shader_;
MeshShader deform_mesh_shader_;
std::unique_ptr<Shader> solid_shader_;
private:
MeshShader mesh_shader_;
MeshShader skel_mesh_shader_;
MeshShader deform_mesh_shader_;
std::unique_ptr<Shader> solid_shader_;
std::unique_ptr<BufferObject> beam_segments_vbo_;
std::unique_ptr<VertexArray> beam_va_;
std::unique_ptr<Shader> beam_shader_;
std::unique_ptr<BufferObject> beam_segments_vbo_;
std::unique_ptr<VertexArray> beam_va_;
std::unique_ptr<Shader> beam_shader_;
std::unique_ptr<Shader> hud_shader_;
std::unique_ptr<Shader> hud_shader_;
const Shader* current_shader_ = nullptr;
};
const Shader* current_shader_ = nullptr;
};
}
} // namespace gfx