void savePrefab(const Path& path) override { auto& selected_entities = m_editor.getSelectedEntities(); if (selected_entities.size() != 1) return; Entity entity = selected_entities[0]; u64 prefab = getPrefab(entity); if (prefab != 0) entity = getPrefabRoot(entity); FS::OsFile file; if (!file.open(path.c_str(), FS::Mode::CREATE_AND_WRITE)) { g_log_error.log("Editor") << "Failed to create " << path.c_str(); return; } Array<Entity> entities(m_editor.getAllocator()); gatherHierarchy(entity, true, entities); OutputBlob blob(m_editor.getAllocator()); SaveEntityGUIDMap entity_map(entities); TextSerializer serializer(blob, entity_map); serializePrefab(m_universe, entities[0], path, serializer); file.write(blob.getData(), blob.getPos()); file.close(); if (prefab == 0) { m_editor.beginCommandGroup(crc32("save_prefab")); Transform tr = m_universe->getTransform(entity); m_editor.destroyEntities(&entities[0], entities.size()); auto* resource_manager = m_editor.getEngine().getResourceManager().get(PrefabResource::TYPE); auto* res = (PrefabResource*)resource_manager->load(path); FS::FileSystem& fs = m_editor.getEngine().getFileSystem(); while (fs.hasWork()) fs.updateAsyncTransactions(); instantiatePrefab(*res, tr.pos, tr.rot, tr.scale); m_editor.endCommandGroup(); } }
Engine* Engine::create(FS::FileSystem* fs, IAllocator& allocator) { g_log_info.log("engine") << "Creating engine..."; Profiler::setThreadName("Main"); installUnhandledExceptionHandler(); g_is_error_file_opened = g_error_file.open("error.log", FS::Mode::CREATE | FS::Mode::WRITE, allocator); g_log_error.getCallback().bind<logErrorToFile>(); g_log_info.getCallback().bind<showLogInVS>(); g_log_warning.getCallback().bind<showLogInVS>(); g_log_error.getCallback().bind<showLogInVS>(); EngineImpl* engine = LUMIX_NEW(allocator, EngineImpl)(fs, allocator); if (!engine->create()) { g_log_error.log("engine") << "Failed to create engine."; LUMIX_DELETE(allocator, engine); return nullptr; } g_log_info.log("engine") << "Engine created."; return engine; }
bool Settings::save() { auto& actions = m_app.getActions(); FS::OsFile file; if (!file.open(SETTINGS_PATH, FS::Mode::CREATE_AND_WRITE)) return false; file << "window = { x = " << m_window.x << ", y = " << m_window.y << ", w = " << m_window.w << ", h = " << m_window.h << " }\n"; file << "maximized = " << (m_is_maximized ? "true" : "false") << "\n"; auto writeBool = [&file](const char* name, bool value) { file << name << " = " << (value ? "true\n" : "false\n"); }; writeBool("settings_opened", m_is_open); writeBool("asset_browser_opened", m_is_asset_browser_open); writeBool("entity_list_opened", m_is_entity_list_open); writeBool("entity_template_list_opened", m_is_entity_template_list_open); writeBool("log_opened", m_is_log_open); writeBool("profiler_opened", m_is_profiler_open); writeBool("properties_opened", m_is_properties_open); writeBool("error_reporting_enabled", m_is_crash_reporting_enabled); file << "mouse_sensitivity_x = " << m_mouse_sensitivity.x << "\n"; file << "mouse_sensitivity_y = " << m_mouse_sensitivity.y << "\n"; file << "font_size = " << m_font_size << "\n"; file << "asset_browser_left_column_width = " << m_asset_browser_left_column_width << "\n"; saveStyle(file); file << "data_dir = \""; const char* c = m_data_dir; while (*c) { if (*c == '\\') file << "\\\\"; else file << *c; ++c; } file << "\"\n"; file << "custom = {\n"; lua_getglobal(m_state, "custom"); lua_pushnil(m_state); bool first = true; while (lua_next(m_state, -2)) { if (!first) file << ",\n"; const char* name = lua_tostring(m_state, -2); switch (lua_type(m_state, -1)) { case LUA_TBOOLEAN: file << name << " = " << (lua_toboolean(m_state, -1) != 0 ? "true" : "false"); break; case LUA_TNUMBER: file << name << " = " << (int)lua_tonumber(m_state, -1); break; default: ASSERT(false); break; } lua_pop(m_state, 1); first = false; } lua_pop(m_state, 1); file << "}\n"; file << "actions = {\n"; for (int i = 0; i < actions.size(); ++i) { file << "\t" << actions[i]->name << " = {" << actions[i]->shortcut[0] << ", " << actions[i]->shortcut[1] << ", " << actions[i]->shortcut[2] << "},\n"; } file << "}\n"; file << "toolbar = {\n"; for (auto* action : m_app.getToolbarActions()) { file << "\t\"" << action->name << "\",\n"; } file << "}\n"; ImGui::SaveDock(file); file.close(); return true; }
void Engine::destroy(Engine* engine, IAllocator& allocator) { LUMIX_DELETE(allocator, engine); g_error_file.close(); }
static void logErrorToFile(const char*, const char* message) { if (!g_is_error_file_opened) return; g_error_file.write(message, stringLength(message)); g_error_file.flush(); }
namespace Lumix { static const uint32 SERIALIZED_ENGINE_MAGIC = 0x5f4c454e; // == '_LEN' static const uint32 HIERARCHY_HASH = crc32("hierarchy"); enum class SerializedEngineVersion : int32 { BASE, SPARSE_TRANFORMATIONS, FOG_PARAMS, SCENE_VERSION, HIERARCHY_COMPONENT, SCENE_VERSION_CHECK, LATEST // must be the last one }; #pragma pack(1) class SerializedEngineHeader { public: uint32 m_magic; SerializedEngineVersion m_version; uint32 m_reserved; // for crc }; #pragma pack() IScene* UniverseContext::getScene(uint32 hash) const { for (auto* scene : m_scenes) { if (crc32(scene->getPlugin().getName()) == hash) { return scene; } } return nullptr; } class EngineImpl : public Engine { public: EngineImpl(FS::FileSystem* fs, IAllocator& allocator) : m_allocator(allocator) , m_resource_manager(m_allocator) , m_mtjd_manager(nullptr) , m_fps(0) , m_is_game_running(false) , m_component_types(m_allocator) , m_last_time_delta(0) , m_path_manager(m_allocator) { m_mtjd_manager = MTJD::Manager::create(m_allocator); if (!fs) { m_file_system = FS::FileSystem::create(m_allocator); m_mem_file_device = LUMIX_NEW(m_allocator, FS::MemoryFileDevice)(m_allocator); m_disk_file_device = LUMIX_NEW(m_allocator, FS::DiskFileDevice)(m_allocator); m_file_system->mount(m_mem_file_device); m_file_system->mount(m_disk_file_device); m_file_system->setDefaultDevice("memory:disk"); m_file_system->setSaveGameDevice("memory:disk"); } else { m_file_system = fs; m_mem_file_device = nullptr; m_disk_file_device = nullptr; } m_resource_manager.create(*m_file_system); m_timer = Timer::create(m_allocator); m_fps_timer = Timer::create(m_allocator); m_fps_frame = 0; } bool create() { m_plugin_manager = PluginManager::create(*this); if (!m_plugin_manager) { return false; } HierarchyPlugin* hierarchy = LUMIX_NEW(m_allocator, HierarchyPlugin)(m_allocator); m_plugin_manager->addPlugin(hierarchy); m_input_system = InputSystem::create(m_allocator); if (!m_input_system) { return false; } return true; } ~EngineImpl() { Timer::destroy(m_timer); Timer::destroy(m_fps_timer); PluginManager::destroy(m_plugin_manager); if (m_input_system) InputSystem::destroy(*m_input_system); if (m_disk_file_device) { FS::FileSystem::destroy(m_file_system); LUMIX_DELETE(m_allocator, m_mem_file_device); LUMIX_DELETE(m_allocator, m_disk_file_device); } m_resource_manager.destroy(); MTJD::Manager::destroy(*m_mtjd_manager); } void setPlatformData(const PlatformData& data) override { m_platform_data = data; } const PlatformData& getPlatformData() override { return m_platform_data; } IAllocator& getAllocator() override { return m_allocator; } UniverseContext& createUniverse() override { UniverseContext* context = LUMIX_NEW(m_allocator, UniverseContext)(m_allocator); context->m_universe = LUMIX_NEW(m_allocator, Universe)(m_allocator); const Array<IPlugin*>& plugins = m_plugin_manager->getPlugins(); for (auto* plugin : plugins) { IScene* scene = plugin->createScene(*context); if (scene) { context->m_scenes.push(scene); } } return *context; } MTJD::Manager& getMTJDManager() override { return *m_mtjd_manager; } void destroyUniverse(UniverseContext& context) override { for (int i = context.m_scenes.size() - 1; i >= 0; --i) { context.m_scenes[i]->getPlugin().destroyScene(context.m_scenes[i]); } LUMIX_DELETE(m_allocator, context.m_universe); LUMIX_DELETE(m_allocator, &context); m_resource_manager.removeUnreferenced(); } PluginManager& getPluginManager() override { return *m_plugin_manager; } FS::FileSystem& getFileSystem() override { return *m_file_system; } void startGame(UniverseContext& context) override { ASSERT(!m_is_game_running); m_is_game_running = true; for (auto* scene : context.m_scenes) { scene->startGame(); } } void stopGame(UniverseContext& context) override { ASSERT(m_is_game_running); m_is_game_running = false; for (auto* scene : context.m_scenes) { scene->stopGame(); } } void update(UniverseContext& context) override { PROFILE_FUNCTION(); float dt; ++m_fps_frame; if (m_fps_timer->getTimeSinceTick() > 0.5f) { m_fps = m_fps_frame / m_fps_timer->tick(); m_fps_frame = 0; } dt = m_timer->tick(); m_last_time_delta = dt; { PROFILE_BLOCK("update scenes"); for (int i = 0; i < context.m_scenes.size(); ++i) { context.m_scenes[i]->update(dt); } } m_plugin_manager->update(dt); m_input_system->update(dt); getFileSystem().updateAsyncTransactions(); } InputSystem& getInputSystem() override { return *m_input_system; } ResourceManager& getResourceManager() override { return m_resource_manager; } float getFPS() const override { return m_fps; } void serializerSceneVersions(OutputBlob& serializer, UniverseContext& ctx) { serializer.write(ctx.m_scenes.size()); for (auto* scene : ctx.m_scenes) { serializer.write(crc32(scene->getPlugin().getName())); serializer.write(scene->getVersion()); } } void serializePluginList(OutputBlob& serializer) { serializer.write((int32)m_plugin_manager->getPlugins().size()); for (auto* plugin : m_plugin_manager->getPlugins()) { serializer.writeString(plugin->getName()); } } bool hasSupportedSceneVersions(InputBlob& serializer, UniverseContext& ctx) { int32 count; serializer.read(count); for (int i = 0; i < count; ++i) { uint32 hash; serializer.read(hash); auto* scene = ctx.getScene(hash); int version; serializer.read(version); if (version > scene->getVersion()) { g_log_error.log("engine") << "Plugin " << scene->getPlugin().getName() << " is too old"; return false; } } return true; } bool hasSerializedPlugins(InputBlob& serializer) { int32 count; serializer.read(count); for (int i = 0; i < count; ++i) { char tmp[32]; serializer.readString(tmp, sizeof(tmp)); if (!m_plugin_manager->getPlugin(tmp)) { g_log_error.log("engine") << "Missing plugin " << tmp; return false; } } return true; } uint32 serialize(UniverseContext& ctx, OutputBlob& serializer) override { SerializedEngineHeader header; header.m_magic = SERIALIZED_ENGINE_MAGIC; // == '_LEN' header.m_version = SerializedEngineVersion::LATEST; header.m_reserved = 0; serializer.write(header); serializePluginList(serializer); serializerSceneVersions(serializer, ctx); m_path_manager.serialize(serializer); int pos = serializer.getSize(); ctx.m_universe->serialize(serializer); m_plugin_manager->serialize(serializer); serializer.write((int32)ctx.m_scenes.size()); for (int i = 0; i < ctx.m_scenes.size(); ++i) { serializer.writeString(ctx.m_scenes[i]->getPlugin().getName()); serializer.write(ctx.m_scenes[i]->getVersion()); ctx.m_scenes[i]->serialize(serializer); } uint32 crc = crc32((const uint8*)serializer.getData() + pos, serializer.getSize() - pos); return crc; } bool deserialize(UniverseContext& ctx, InputBlob& serializer) override { SerializedEngineHeader header; serializer.read(header); if (header.m_magic != SERIALIZED_ENGINE_MAGIC) { g_log_error.log("engine") << "Wrong or corrupted file"; return false; } if (header.m_version > SerializedEngineVersion::LATEST) { g_log_error.log("engine") << "Unsupported version"; return false; } if (!hasSerializedPlugins(serializer)) { return false; } if (header.m_version > SerializedEngineVersion::SCENE_VERSION_CHECK && !hasSupportedSceneVersions(serializer, ctx)) { return false; } m_path_manager.deserialize(serializer); ctx.m_universe->deserialize(serializer); if (header.m_version <= SerializedEngineVersion::HIERARCHY_COMPONENT) { ctx.getScene(HIERARCHY_HASH)->deserialize(serializer, 0); } m_plugin_manager->deserialize(serializer); int32 scene_count; serializer.read(scene_count); for (int i = 0; i < scene_count; ++i) { char tmp[32]; serializer.readString(tmp, sizeof(tmp)); IScene* scene = ctx.getScene(crc32(tmp)); int scene_version = -1; if (header.m_version > SerializedEngineVersion::SCENE_VERSION) { serializer.read(scene_version); } scene->deserialize(serializer, scene_version); } m_path_manager.clear(); return true; } float getLastTimeDelta() override { return m_last_time_delta; } private: struct ComponentType { ComponentType(IAllocator& allocator) : m_name(allocator) , m_id(allocator) { } string m_name; string m_id; uint32 m_id_hash; uint32 m_dependency; }; private: Debug::Allocator m_allocator; FS::FileSystem* m_file_system; FS::MemoryFileDevice* m_mem_file_device; FS::DiskFileDevice* m_disk_file_device; ResourceManager m_resource_manager; MTJD::Manager* m_mtjd_manager; Array<ComponentType> m_component_types; PluginManager* m_plugin_manager; InputSystem* m_input_system; Timer* m_timer; Timer* m_fps_timer; int m_fps_frame; float m_fps; float m_last_time_delta; bool m_is_game_running; PlatformData m_platform_data; PathManager m_path_manager; private: void operator=(const EngineImpl&); EngineImpl(const EngineImpl&); }; static void showLogInVS(const char* system, const char* message) { Debug::debugOutput(system); Debug::debugOutput(" : "); Debug::debugOutput(message); Debug::debugOutput("\n"); } static FS::OsFile g_error_file; static bool g_is_error_file_opened = false; static void logErrorToFile(const char*, const char* message) { if (!g_is_error_file_opened) return; g_error_file.write(message, stringLength(message)); g_error_file.flush(); } Engine* Engine::create(FS::FileSystem* fs, IAllocator& allocator) { g_log_info.log("engine") << "Creating engine..."; Profiler::setThreadName("Main"); installUnhandledExceptionHandler(); g_is_error_file_opened = g_error_file.open("error.log", FS::Mode::CREATE | FS::Mode::WRITE, allocator); g_log_error.getCallback().bind<logErrorToFile>(); g_log_info.getCallback().bind<showLogInVS>(); g_log_warning.getCallback().bind<showLogInVS>(); g_log_error.getCallback().bind<showLogInVS>(); EngineImpl* engine = LUMIX_NEW(allocator, EngineImpl)(fs, allocator); if (!engine->create()) { g_log_error.log("engine") << "Failed to create engine."; LUMIX_DELETE(allocator, engine); return nullptr; } g_log_info.log("engine") << "Engine created."; return engine; } void Engine::destroy(Engine* engine, IAllocator& allocator) { LUMIX_DELETE(allocator, engine); g_error_file.close(); } } // ~namespace Lumix