worlds can be saved in any directory

This commit is contained in:
Mikulas Florek 2023-09-22 23:18:44 +02:00
parent 47e554d3c6
commit 5dc392e271
17 changed files with 138 additions and 161 deletions

Binary file not shown.

Binary file not shown.

View File

@ -5,7 +5,7 @@ function onInputEvent(event : InputEvent)
local old_partition = this.world:getActivePartition()
local demo = this.world:createPartition("demo")
this.world:setActivePartition(demo)
this.world:load("demo", function()
this.world:load("universes/demo.unv", function()
--this.world:destroyPartition(old_partition)
end)

View File

@ -47,6 +47,27 @@ bool AssetBrowser::IPlugin::createTile(const char* in_path, const char* out_path
return false;
}
static ResourceType WORLD_TYPE("world");
struct WorldAssetPlugin final : AssetBrowser::IPlugin, AssetCompiler::IPlugin {
explicit WorldAssetPlugin(StudioApp& app)
: m_app(app)
{
app.getAssetCompiler().registerExtension("unv", WORLD_TYPE);
}
void addSubresources(AssetCompiler& compiler, const Path& path) override {
compiler.addResource(WORLD_TYPE, path);
}
void openEditor(const Path& path) override { m_app.tryLoadWorld(path, false); }
bool compile(const Path& src) override { return true; }
const char* getLabel() const override { return "World"; }
StudioApp& m_app;
};
struct AssetBrowserImpl : AssetBrowser {
struct FileInfo {
StaticString<MAX_PATH> clamped_filename;
@ -74,6 +95,7 @@ struct AssetBrowserImpl : AssetBrowser {
, m_immediate_tiles(m_allocator)
, m_subdirs(m_allocator)
, m_windows(m_allocator)
, m_world_asset_plugin(app)
{
PROFILE_FUNCTION();
@ -93,6 +115,9 @@ struct AssetBrowserImpl : AssetBrowser {
m_app.addAction(&m_back_action);
m_app.addAction(&m_forward_action);
m_app.addWindowAction(&m_toggle_ui);
const char* world_exts[] = { "unv" };
addPlugin(m_world_asset_plugin, Span(world_exts));
m_app.getAssetCompiler().addPlugin(m_world_asset_plugin, Span(world_exts));
}
void focusSearch() {
@ -1242,6 +1267,7 @@ struct AssetBrowserImpl : AssetBrowser {
Action m_toggle_ui;
Action m_back_action;
Action m_forward_action;
WorldAssetPlugin m_world_asset_plugin;
};
UniquePtr<AssetBrowser> AssetBrowser::create(StudioApp& app) {

View File

@ -89,7 +89,6 @@ struct StudioAppImpl final : StudioApp
, m_component_labels(m_allocator)
, m_component_icons(m_allocator)
, m_exit_code(0)
, m_worlds(m_allocator)
, m_events(m_allocator)
, m_windows(m_allocator)
, m_deferred_destroy_windows(m_allocator)
@ -377,7 +376,6 @@ struct StudioAppImpl final : StudioApp
m_asset_compiler = AssetCompiler::create(*this);
m_editor = WorldEditor::create(*m_engine, m_allocator);
m_editor->entitySelectionChanged().bind<&StudioAppImpl::onEntitySelectionChanged>(this);
scanWorlds();
loadUserPlugins();
addActions();
@ -919,28 +917,29 @@ struct StudioAppImpl final : StudioApp
m_editor->endCommandGroup();
}
void loadWorld(const char* name, bool additive) {
const char* base_path = m_engine->getFileSystem().getBasePath();
os::InputFile file;
const Path path(base_path, "universes/", name, ".unv");
void tryLoadWorld(const Path& path, bool additive) override {
if (!additive && m_editor->isWorldChanged()) {
m_world_to_load = path;
m_confirm_load = true;
}
else {
loadWorld(path, additive);
}
}
if (!file.open(path.c_str())) {
logError("Failed to open ", path);
void loadWorld(const Path& path, bool additive) {
FileSystem& fs = m_engine->getFileSystem();
OutputMemoryStream data(m_allocator);
if (!fs.getContentSync(path, data)) {
logError("Failed to read ", path);
m_editor->newWorld();
return;
}
OutputMemoryStream data(m_allocator);
data.resize(file.size());
if (!file.read(data.getMutableData(), data.size())) {
logError("Failed to read ", path);
file.close();
return;
}
file.close();
InputMemoryStream blob(data);
m_editor->loadWorld(blob, name, additive);
InputMemoryStream blob(data);
m_editor->loadWorld(blob, path.c_str(), additive);
}
void guiWelcomeScreen()
@ -988,7 +987,6 @@ struct StudioAppImpl final : StudioApp
m_engine->getFileSystem().setBasePath(dir);
extractBundled();
m_editor->loadProject();
scanWorlds();
m_asset_compiler->onBasePathChanged();
m_engine->getResourceManager().reloadAll();
}
@ -1000,17 +998,12 @@ struct StudioAppImpl final : StudioApp
}
ImGui::Text("Open world:");
ImGui::Indent();
if(m_worlds.empty()) {
ImGui::Text("No worlds found");
}
for (auto& univ : m_worlds)
{
if (ImGui::MenuItem(univ))
{
loadWorld(univ, false);
forEachWorld([&](const Path& path){
if (ImGui::MenuItem(path.c_str())) {
loadWorld(path, false);
m_is_welcome_screen_open = false;
}
}
});
ImGui::Unindent();
}
ImGui::EndChild();
@ -1066,36 +1059,13 @@ struct StudioAppImpl final : StudioApp
}
void guiSaveAsDialog() {
if (m_save_as_request) {
openCenterStrip("Save World As");
m_save_as_request = false;
}
if (beginCenterStrip("Save World As", 6)) {
ImGui::NewLine();
static char name[64] = "";
alignGUICenter([&](){
ImGui::TextUnformatted("Save world as");
ImGui::SameLine();
ImGui::SetNextItemWidth(250);
ImGui::InputText("##name", name, sizeof(name));
});
ImGui::NewLine();
alignGUICenter([&](){
if (ImGui::Button(ICON_FA_SAVE "Save")) {
ASSERT(!m_editor->isGameMode());
World* world = m_editor->getWorld();
World::PartitionHandle active_partition_handle = world->getActivePartition();
World::Partition& active_partition = world->getPartition(active_partition_handle);
copyString(active_partition.name, name);
m_editor->savePartition(active_partition_handle);
scanWorlds();
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_TIMES "Cancel")) ImGui::CloseCurrentPopup();
});
endCenterStrip();
if (m_file_selector.gui("Save world as", &m_show_save_world_ui, "unv", true)) {
ASSERT(!m_editor->isGameMode());
World* world = m_editor->getWorld();
World::PartitionHandle active_partition_handle = world->getActivePartition();
World::Partition& active_partition = world->getPartition(active_partition_handle);
copyString(active_partition.name, m_file_selector.getPath());
m_editor->savePartition(active_partition_handle);
}
}
@ -1106,7 +1076,7 @@ struct StudioAppImpl final : StudioApp
return;
}
m_save_as_request = true;
m_show_save_world_ui = true;
}
void exit() {
@ -1445,6 +1415,17 @@ struct StudioAppImpl final : StudioApp
ImGui::EndMenu();
}
template <typename T>
void forEachWorld(T& f) {
const HashMap<FilePathHash, AssetCompiler::ResourceItem>& resources = m_asset_compiler->lockResources();
ResourceType WORLD_TYPE("world");
for (const AssetCompiler::ResourceItem& ri : resources) {
if (ri.type != WORLD_TYPE) continue;
f(ri.path);
}
m_asset_compiler->unlockResources();
}
void fileMenu()
{
@ -1456,17 +1437,11 @@ struct StudioAppImpl final : StudioApp
if (ImGui::BeginMenu(label)) {
m_open_filter.gui("Filter", 150);
for (auto& univ : m_worlds) {
if (m_open_filter.pass(univ) && ImGui::MenuItem(univ)) {
if (!additive && m_editor->isWorldChanged()) {
m_world_to_load = univ;
m_confirm_load = true;
}
else {
loadWorld(univ, additive);
}
forEachWorld([&](const Path& path){
if (m_open_filter.pass(path.c_str()) && ImGui::MenuItem(path.c_str())) {
tryLoadWorld(path, additive);
}
}
});
ImGui::EndMenu();
}
};
@ -2568,7 +2543,7 @@ struct StudioAppImpl final : StudioApp
if (!parser.next()) break;
parser.getCurrent(path, lengthOf(path));
loadWorld(path, false);
loadWorld(Path(path), false);
m_is_welcome_screen_open = false;
break;
}
@ -3204,13 +3179,19 @@ struct StudioAppImpl final : StudioApp
}
ImGuiEx::Label("Startup world");
if (m_worlds.size() > 0 && m_export.startup_world[0] == '\0') m_export.startup_world = m_worlds[0].data;
if (ImGui::BeginCombo("##startunv", m_export.startup_world)) {
for (const auto& unv : m_worlds) {
if (ImGui::Selectable(unv)) m_export.startup_world = unv.data;
}
if (ImGui::BeginCombo("##startunv", m_export.startup_world.c_str())) {
forEachWorld([&](const Path& path){
if (ImGui::Selectable(path.c_str())) m_export.startup_world = path;
});
ImGui::EndCombo();
}
if (m_export.startup_world.isEmpty()) {
forEachWorld([&](const Path& path){
if (m_export.startup_world.isEmpty()) {
m_export.startup_world = path;
}
});
}
if (m_export_msg_timer > 0) {
m_export_msg_timer -= m_engine->getLastTimeDelta();
@ -3336,25 +3317,6 @@ struct StudioAppImpl final : StudioApp
return true;
}
void scanWorlds() override {
PROFILE_FUNCTION();
m_worlds.clear();
auto* iter = m_engine->getFileSystem().createFileIterator("universes");
os::FileInfo info;
while (os::getNextFile(iter, &info))
{
if (info.filename[0] == '.') continue;
if (info.is_directory) continue;
if (startsWith(info.filename, "__")) continue;
if (!Path::hasExtension(info.filename, "unv")) continue;
m_worlds.emplace(Path::getBasename(info.filename));
}
os::destroyFileIterator(iter);
}
Span<const os::Event> getEvents() const override { return m_events; }
void checkShortcuts() {
@ -3432,13 +3394,12 @@ struct StudioAppImpl final : StudioApp
Array<IPlugin*> m_plugins;
Array<IAddComponentPlugin*> m_add_cmp_plugins;
Array<StaticString<MAX_PATH>> m_worlds;
AddCmpTreeNode m_add_cmp_root;
HashMap<ComponentType, String> m_component_labels;
HashMap<ComponentType, StaticString<5>> m_component_icons;
Gizmo::Config m_gizmo_config;
bool m_save_as_request = false;
bool m_show_save_world_ui = false;
bool m_cursor_captured = false;
bool m_confirm_exit = false;
bool m_confirm_load = false;
@ -3447,7 +3408,7 @@ struct StudioAppImpl final : StudioApp
bool m_is_caption_hovered = false;
World::PartitionHandle m_partition_to_destroy;
StaticString<MAX_PATH> m_world_to_load;
Path m_world_to_load;
ImTextureID m_logo = nullptr;
UniquePtr<AssetBrowser> m_asset_browser;
@ -3480,7 +3441,7 @@ struct StudioAppImpl final : StudioApp
Mode mode = Mode::ALL_FILES;
bool pack = false;
StaticString<96> startup_world;
Path startup_world;
StaticString<MAX_PATH> dest_dir;
};

View File

@ -96,6 +96,7 @@ struct LUMIX_EDITOR_API StudioApp {
virtual struct Settings& getSettings() = 0;
virtual struct RenderInterface* getRenderInterface() = 0;
virtual void setRenderInterface(RenderInterface* ifc) = 0;
virtual void tryLoadWorld(const struct Path& path, bool additive) = 0;
virtual void addPlugin(IPlugin& plugin) = 0;
virtual void addPlugin(MousePlugin& plugin) = 0;
@ -119,7 +120,6 @@ struct LUMIX_EDITOR_API StudioApp {
virtual void addWindowAction(Action* action) = 0;
virtual Action* getAction(const char* name) = 0;
virtual void scanWorlds() = 0;
virtual void runScript(const char* src, const char* script_name) = 0;
virtual void setFullscreen(bool fullscreen) = 0;
virtual void snapDown() = 0;

View File

@ -2067,28 +2067,30 @@ bool FileSelector::gui(const char* label, bool* open, const char* extension, boo
if (ImGui::BeginPopupModal(label, nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
res = gui(true, extension);
if (m_save) {
if (ImGui::Button(ICON_FA_SAVE " Save")) {
if (!Path::hasExtension(m_full_path, m_accepted_extension)) {
m_full_path.append(".", m_accepted_extension.c_str());
}
if (m_app.getEngine().getFileSystem().fileExists(m_full_path)) {
ImGui::OpenPopup("warn_overwrite");
}
else {
res = true;
alignGUICenter([&](){
if (m_save) {
if (ImGui::Button(ICON_FA_SAVE " Save")) {
if (!Path::hasExtension(m_full_path, m_accepted_extension)) {
m_full_path.append(".", m_accepted_extension.c_str());
}
if (m_app.getEngine().getFileSystem().fileExists(m_full_path)) {
ImGui::OpenPopup("warn_overwrite");
}
else {
res = true;
}
}
}
}
else {
if (ImGui::Button(ICON_FA_FOLDER_OPEN " Open")) {
if (m_app.getEngine().getFileSystem().fileExists(m_full_path)) {
res = true;
else {
if (ImGui::Button(ICON_FA_FOLDER_OPEN " Open")) {
if (m_app.getEngine().getFileSystem().fileExists(m_full_path)) {
res = true;
}
}
}
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_TIMES " Cancel")) ImGui::CloseCurrentPopup();
ImGui::SameLine();
if (ImGui::Button(ICON_FA_TIMES " Cancel")) ImGui::CloseCurrentPopup();
});
if (ImGui::BeginPopup("warn_overwrite")) {
ImGui::TextUnformatted("File already exists, are you sure you want to overwrite it?");

View File

@ -1966,28 +1966,26 @@ public:
void save() {
ASSERT(m_world->getPartitions().size() == 1);
const char* basename = m_world->getPartitions()[0].name;
ASSERT(basename[0]);
const char* path = m_world->getPartitions()[0].name;
ASSERT(path[0]);
saveProject();
logInfo("Saving world ", basename, "...");
logInfo("Saving world ", path, "...");
StaticString<MAX_PATH> path(m_engine.getFileSystem().getBasePath(), "universes");
if (!os::makePath(path)) logError("Could not create directory universes/");
path.append("/", basename, ".unv");
StaticString<MAX_PATH> bkp_path(path, ".bak");
if (os::fileExists(path)) {
if (!os::copyFile(path, bkp_path)) {
FileSystem& fs = m_engine.getFileSystem();
Path bkp_path(path, ".bak");
if (fs.fileExists(path)) {
if (!fs.copyFile(path, bkp_path)) {
logError("Could not copy ", path, " to ", bkp_path);
}
}
os::OutputFile file;
if (file.open(path)) {
if (fs.open(path, file)) {
save(file, false);
file.close();
}
else {
logError("Failed to save world ", basename);
logError("Failed to save world ", path);
}
m_is_world_changed = false;
@ -2467,7 +2465,7 @@ public:
void saveProject() {
FileSystem& fs = m_engine.getFileSystem();
OutputMemoryStream blob(m_allocator);
m_engine.serializeProject(blob, "main");
m_engine.serializeProject(blob, Path("main.unv"));
if (!fs.saveContentSync(Path("lumix.prj"), blob)) {
logError("Failed to save lumix.prj");
@ -2482,9 +2480,9 @@ public:
}
InputMemoryStream stream(data);
char dummy[1];
Path dummy;
const DeserializeProjectResult res = m_engine.deserializeProject(stream, Span(dummy));
const DeserializeProjectResult res = m_engine.deserializeProject(stream, dummy);
switch (res) {
case DeserializeProjectResult::SUCCESS: break;
case DeserializeProjectResult::PLUGIN_DESERIALIZATION_FAILED: logError("Project file: Plugin deserialization failed"); break;

View File

@ -392,14 +392,13 @@ struct EngineImpl final : Engine {
ProjectVersion version;
};
DeserializeProjectResult deserializeProject(InputMemoryStream& serializer, Span<char> startup_world) override {
DeserializeProjectResult deserializeProject(InputMemoryStream& serializer, Path& startup_world) override {
ProjectHeader header;
serializer.read(header);
if (header.magic != SERIALIZED_PROJECT_MAGIC) return DeserializeProjectResult::CORRUPTED_FILE;
if (header.version > ProjectVersion::LAST) return DeserializeProjectResult::VERSION_NOT_SUPPORTED;
if (header.version <= ProjectVersion::HASH64) return DeserializeProjectResult::VERSION_NOT_SUPPORTED;
const char* tmp = serializer.readString();
copyString(startup_world, tmp);
startup_world = serializer.readString();
i32 count = 0;
serializer.read(count);
const Array<ISystem*>& systems = m_system_manager->getSystems();
@ -420,12 +419,12 @@ struct EngineImpl final : Engine {
return DeserializeProjectResult::SUCCESS;
}
void serializeProject(OutputMemoryStream& serializer, const char* startup_world) const override {
void serializeProject(OutputMemoryStream& serializer, const Path& startup_world) const override {
ProjectHeader header;
header.magic = SERIALIZED_PROJECT_MAGIC;
header.version = ProjectVersion::LAST;
serializer.write(header);
serializer.writeString(startup_world);
serializer.writeString(startup_world.c_str());
const Array<ISystem*>& systems = m_system_manager->getSystems();
serializer.write((i32)systems.size());
for (ISystem* system : systems) {

View File

@ -55,8 +55,8 @@ struct LUMIX_ENGINE_API Engine {
virtual void stopGame(World& world) = 0;
virtual void update(World& world) = 0;
[[nodiscard]] virtual DeserializeProjectResult deserializeProject(struct InputMemoryStream& serializer, Span<char> startup_world) = 0;
virtual void serializeProject(struct OutputMemoryStream& serializer, const char* startup_world) const = 0;
[[nodiscard]] virtual DeserializeProjectResult deserializeProject(struct InputMemoryStream& serializer, Path& startup_world) = 0;
virtual void serializeProject(struct OutputMemoryStream& serializer, const Path& startup_world) const = 0;
virtual float getLastTimeDelta() const = 0;
virtual void setTimeMultiplier(float multiplier) = 0;
virtual void pause(bool pause) = 0;

View File

@ -724,7 +724,7 @@ static int LUA_loadWorld(lua_State* L)
{
Engine* engine = getEngineUpvalue(L);
auto* world = LuaWrapper::checkArg<World*>(L, 1);
auto* name = LuaWrapper::checkArg<const char*>(L, 2);
auto* path = LuaWrapper::checkArg<const char*>(L, 2);
if (!lua_isfunction(L, 3)) LuaWrapper::argError(L, 3, "function");
struct Callback {
~Callback() { LuaWrapper::luaL_unref(L, LUA_REGISTRYINDEX, lua_func); }
@ -764,8 +764,7 @@ static int LUA_loadWorld(lua_State* L)
Callback* inst = LUMIX_NEW(engine->getAllocator(), Callback);
inst->engine = engine;
inst->world = world;
const Path path("universes/", name, ".unv");
inst->path = path;
inst->path = Path(path);
inst->L = L;
inst->lua_func = LuaWrapper::luaL_ref(L, LUA_REGISTRYINDEX);
fs.getContent(inst->path, makeDelegate<&Callback::invoke>(inst));

View File

@ -114,7 +114,7 @@ struct PropertyGridPlugin final : PropertyGrid::IPlugin {
if(module->isNavmeshReady(entities[0])) {
ImGui::SameLine();
if (ImGui::Button("Save")) {
const Path dir(m_app.getEngine().getFileSystem().getBasePath(), "/universes/navzones/");
const Path dir(m_app.getEngine().getFileSystem().getBasePath(), "/navzones/");
if (!os::makePath(dir.c_str()) && !os::dirExists(dir)) {
logError("Could not create ", dir);
}

View File

@ -682,7 +682,7 @@ struct NavigationModuleImpl final : NavigationModule
LoadCallback* lcb = LUMIX_NEW(m_allocator, LoadCallback)(*this, zone_entity);
const Path path("universes/navzones/", zone.zone.guid, ".nav");
const Path path("navzones/", zone.zone.guid, ".nav");
FileSystem& fs = m_engine.getFileSystem();
return fs.getContent(path, makeDelegate<&LoadCallback::fileLoaded>(lcb)).isValid();
}
@ -694,7 +694,7 @@ struct NavigationModuleImpl final : NavigationModule
FileSystem& fs = m_engine.getFileSystem();
os::OutputFile file;
const Path path("universes/navzones/", zone.zone.guid, ".nav");
const Path path("navzones/", zone.zone.guid, ".nav");
if (!fs.open(path, file)) return false;
bool success = file.write(zone.m_num_tiles_x);

View File

@ -3393,11 +3393,7 @@ struct EnvironmentProbePlugin final : PropertyGrid::IPlugin
bool saveCubemap(u64 probe_guid, const Vec4* data, u32 texture_size, u32 mips_count) {
ASSERT(data);
const char* base_path = m_app.getEngine().getFileSystem().getBasePath();
Path path(base_path, "universes");
if (!os::makePath(path.c_str()) && !os::dirExists(path)) {
logError("Failed to create ", path);
}
path.append("/probes_tmp/");
Path path(base_path, "probes_tmp/");
if (!os::makePath(path.c_str()) && !os::dirExists(path)) {
logError("Failed to create ", path);
}
@ -3555,11 +3551,7 @@ struct EnvironmentProbePlugin final : PropertyGrid::IPlugin
if (m_done_counter == m_probe_counter && !m_probes.empty()) {
const char* base_path = m_app.getEngine().getFileSystem().getBasePath();
Path dir_path(base_path, "universes/");
if (!os::dirExists(dir_path) && !os::makePath(dir_path.c_str())) {
logError("Failed to create ", dir_path);
}
dir_path.append("/probes/");
Path dir_path(base_path, "probes/");
if (!os::dirExists(dir_path) && !os::makePath(dir_path.c_str())) {
logError("Failed to create ", dir_path);
}
@ -3574,8 +3566,8 @@ struct EnvironmentProbePlugin final : PropertyGrid::IPlugin
const u64 guid = job.reflection_probe.guid;
const Path tmp_path(base_path, "/universes/probes_tmp/", guid, ".lbc");
const Path path(base_path, "/universes/probes/", guid, ".lbc");
const Path tmp_path(base_path, "/probes_tmp/", guid, ".lbc");
const Path path(base_path, "/probes/", guid, ".lbc");
if (!os::fileExists(tmp_path)) {
if (module) module->reloadReflectionProbes();
return;
@ -3736,7 +3728,7 @@ struct EnvironmentProbePlugin final : PropertyGrid::IPlugin
else {
const ReflectionProbe& probe = module->getReflectionProbe(e);
if (probe.flags & ReflectionProbe::ENABLED) {
const Path path("universes/probes/", probe.guid, ".lbc");
const Path path("probes/", probe.guid, ".lbc");
ImGuiEx::Label("Path");
ImGuiEx::TextUnformatted(path);
if (ImGui::Button("View radiance")) m_app.getAssetBrowser().openEditor(path);

View File

@ -829,7 +829,7 @@ struct RenderModuleImpl final : RenderModule {
}
}
const Path path("universes/probes/", probe.guid, ".lbc");
const Path path("probes/", probe.guid, ".lbc");
if (probe.texture_id == 0xffFFffFF) {
logError("There's not enough space for ", path);
return;
@ -845,7 +845,7 @@ struct RenderModuleImpl final : RenderModule {
u32 count;
serializer.read(count);
m_environment_probes.reserve(count + m_environment_probes.size());
const Path probe_dir("universes/probes/");
const Path probe_dir("probes/");
for (u32 i = 0; i < count; ++i) {
EntityRef entity;
serializer.read(entity);