mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2025-10-17 12:09:03 +00:00
Merge pull request #540 from Laupetin/feature/modman-unlink
feat: modman unlinking
This commit is contained in:
@@ -364,14 +364,16 @@ class LinkerImpl final : public Linker
|
||||
zoneDirectory = fs::current_path();
|
||||
auto absoluteZoneDirectory = absolute(zoneDirectory).string();
|
||||
|
||||
auto zone = ZoneLoading::LoadZone(zonePath);
|
||||
if (!zone)
|
||||
auto maybeZone = ZoneLoading::LoadZone(zonePath);
|
||||
if (!maybeZone)
|
||||
{
|
||||
con::error("Failed to load zone \"{}\".", zonePath);
|
||||
con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error());
|
||||
return false;
|
||||
}
|
||||
|
||||
con::debug("Load zone \"{}\"", zone->m_name);
|
||||
auto zone = std::move(*maybeZone);
|
||||
|
||||
con::debug("Loaded zone \"{}\"", zone->m_name);
|
||||
|
||||
m_loaded_zones.emplace_back(std::move(zone));
|
||||
}
|
||||
|
@@ -47,10 +47,16 @@ function ModMan:project()
|
||||
|
||||
self:include(includes)
|
||||
Utils:include(includes)
|
||||
ZoneLoading:include(includes)
|
||||
ObjLoading:include(includes)
|
||||
ObjWriting:include(includes)
|
||||
json:include(includes)
|
||||
webview:include(includes)
|
||||
|
||||
links:linkto(Utils)
|
||||
links:linkto(ZoneLoading)
|
||||
links:linkto(ObjLoading)
|
||||
links:linkto(ObjWriting)
|
||||
links:linkto(webview)
|
||||
links:linkall()
|
||||
end
|
||||
|
42
src/ModMan/Context/FastFileContext.cpp
Normal file
42
src/ModMan/Context/FastFileContext.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#include "FastFileContext.h"
|
||||
|
||||
#include "Web/Binds/ZoneBinds.h"
|
||||
#include "Web/UiCommunication.h"
|
||||
#include "ZoneLoading.h"
|
||||
|
||||
void FastFileContext::Destroy()
|
||||
{
|
||||
// Unload all zones
|
||||
m_loaded_zones.clear();
|
||||
}
|
||||
|
||||
result::Expected<Zone*, std::string> FastFileContext::LoadFastFile(const std::string& path)
|
||||
{
|
||||
auto zone = ZoneLoading::LoadZone(path);
|
||||
if (!zone)
|
||||
return result::Unexpected(std::move(zone.error()));
|
||||
|
||||
auto* result = m_loaded_zones.emplace_back(std::move(*zone)).get();
|
||||
|
||||
ui::NotifyZoneLoaded(result->m_name, path);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
result::Expected<NoResult, std::string> FastFileContext::UnloadZone(const std::string& zoneName)
|
||||
{
|
||||
const auto existingZone = std::ranges::find_if(m_loaded_zones,
|
||||
[&zoneName](const std::unique_ptr<Zone>& zone)
|
||||
{
|
||||
return zone->m_name == zoneName;
|
||||
});
|
||||
|
||||
if (existingZone != m_loaded_zones.end())
|
||||
{
|
||||
m_loaded_zones.erase(existingZone);
|
||||
ui::NotifyZoneUnloaded(zoneName);
|
||||
return NoResult();
|
||||
}
|
||||
|
||||
return result::Unexpected(std::format("No zone with name {} loaded", zoneName));
|
||||
}
|
18
src/ModMan/Context/FastFileContext.h
Normal file
18
src/ModMan/Context/FastFileContext.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "Utils/Result.h"
|
||||
#include "Zone/Zone.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class FastFileContext
|
||||
{
|
||||
public:
|
||||
void Destroy();
|
||||
|
||||
result::Expected<Zone*, std::string> LoadFastFile(const std::string& path);
|
||||
result::Expected<NoResult, std::string> UnloadZone(const std::string& zoneName);
|
||||
|
||||
std::vector<std::unique_ptr<Zone>> m_loaded_zones;
|
||||
};
|
18
src/ModMan/Context/ModManContext.cpp
Normal file
18
src/ModMan/Context/ModManContext.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include "ModManContext.h"
|
||||
|
||||
ModManContext& ModManContext::Get()
|
||||
{
|
||||
static ModManContext context;
|
||||
return context;
|
||||
}
|
||||
|
||||
void ModManContext::Startup()
|
||||
{
|
||||
m_db_thread.Start();
|
||||
}
|
||||
|
||||
void ModManContext::Destroy()
|
||||
{
|
||||
m_fast_file.Destroy();
|
||||
m_db_thread.Terminate();
|
||||
}
|
22
src/ModMan/Context/ModManContext.h
Normal file
22
src/ModMan/Context/ModManContext.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "FastFileContext.h"
|
||||
#include "Utils/DispatchableThread.h"
|
||||
#include "Web/WebViewLib.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class ModManContext
|
||||
{
|
||||
public:
|
||||
static ModManContext& Get();
|
||||
|
||||
void Startup();
|
||||
void Destroy();
|
||||
|
||||
std::unique_ptr<webview::webview> m_main_webview;
|
||||
std::unique_ptr<webview::webview> m_dev_tools_webview;
|
||||
FastFileContext m_fast_file;
|
||||
|
||||
DispatchableThread m_db_thread;
|
||||
};
|
105
src/ModMan/ModManArgs.cpp
Normal file
105
src/ModMan/ModManArgs.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
#include "ModManArgs.h"
|
||||
|
||||
#include "GitVersion.h"
|
||||
#include "Utils/Arguments/UsageInformation.h"
|
||||
#include "Utils/Logging/Log.h"
|
||||
|
||||
#include <format>
|
||||
#include <type_traits>
|
||||
|
||||
namespace
|
||||
{
|
||||
// clang-format off
|
||||
const CommandLineOption* const OPTION_HELP =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithShortName("?")
|
||||
.WithLongName("help")
|
||||
.WithDescription("Displays usage information.")
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_VERSION =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithLongName("version")
|
||||
.WithDescription("Prints the application version.")
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_VERBOSE =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithShortName("v")
|
||||
.WithLongName("verbose")
|
||||
.WithDescription("Outputs a lot more and more detailed messages.")
|
||||
.Build();
|
||||
|
||||
const CommandLineOption* const OPTION_NO_COLOR =
|
||||
CommandLineOption::Builder::Create()
|
||||
.WithLongName("no-color")
|
||||
.WithDescription("Disables colored terminal output.")
|
||||
.Build();
|
||||
// clang-format on
|
||||
|
||||
const CommandLineOption* const COMMAND_LINE_OPTIONS[]{
|
||||
OPTION_HELP,
|
||||
OPTION_VERSION,
|
||||
OPTION_VERBOSE,
|
||||
OPTION_NO_COLOR,
|
||||
};
|
||||
} // namespace
|
||||
|
||||
ModManArgs::ModManArgs()
|
||||
: m_argument_parser(COMMAND_LINE_OPTIONS, std::extent_v<decltype(COMMAND_LINE_OPTIONS)>)
|
||||
{
|
||||
}
|
||||
|
||||
void ModManArgs::PrintUsage() const
|
||||
{
|
||||
UsageInformation usage(m_argument_parser.GetExecutableName());
|
||||
|
||||
for (const auto* commandLineOption : COMMAND_LINE_OPTIONS)
|
||||
{
|
||||
usage.AddCommandLineOption(commandLineOption);
|
||||
}
|
||||
|
||||
usage.Print();
|
||||
}
|
||||
|
||||
void ModManArgs::PrintVersion()
|
||||
{
|
||||
con::info("OpenAssetTools ModMan {}", GIT_VERSION);
|
||||
}
|
||||
|
||||
bool ModManArgs::ParseArgs(const int argc, const char** argv, bool& shouldContinue)
|
||||
{
|
||||
shouldContinue = true;
|
||||
if (!m_argument_parser.ParseArguments(argc, argv))
|
||||
{
|
||||
PrintUsage();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the user requested help
|
||||
if (m_argument_parser.IsOptionSpecified(OPTION_HELP))
|
||||
{
|
||||
PrintUsage();
|
||||
shouldContinue = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the user wants to see the version
|
||||
if (m_argument_parser.IsOptionSpecified(OPTION_VERSION))
|
||||
{
|
||||
PrintVersion();
|
||||
shouldContinue = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// -v; --verbose
|
||||
if (m_argument_parser.IsOptionSpecified(OPTION_VERBOSE))
|
||||
con::globalLogLevel = con::LogLevel::DEBUG;
|
||||
else
|
||||
con::globalLogLevel = con::LogLevel::INFO;
|
||||
|
||||
// --no-color
|
||||
con::globalUseColor = !m_argument_parser.IsOptionSpecified(OPTION_NO_COLOR);
|
||||
|
||||
return true;
|
||||
}
|
19
src/ModMan/ModManArgs.h
Normal file
19
src/ModMan/ModManArgs.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "Utils/Arguments/ArgumentParser.h"
|
||||
|
||||
class ModManArgs
|
||||
{
|
||||
public:
|
||||
ModManArgs();
|
||||
bool ParseArgs(int argc, const char** argv, bool& shouldContinue);
|
||||
|
||||
private:
|
||||
/**
|
||||
* \brief Prints a command line usage help text for the ModMan tool to stdout.
|
||||
*/
|
||||
void PrintUsage() const;
|
||||
static void PrintVersion();
|
||||
|
||||
ArgumentParser m_argument_parser;
|
||||
};
|
77
src/ModMan/Utils/DispatchableThread.cpp
Normal file
77
src/ModMan/Utils/DispatchableThread.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "DispatchableThread.h"
|
||||
|
||||
DispatchableThread::DispatchableThread()
|
||||
: m_terminate(false)
|
||||
{
|
||||
}
|
||||
|
||||
DispatchableThread::~DispatchableThread()
|
||||
{
|
||||
Terminate();
|
||||
}
|
||||
|
||||
void DispatchableThread::Start()
|
||||
{
|
||||
m_terminate = false;
|
||||
m_thread = std::thread(
|
||||
[&]
|
||||
{
|
||||
ThreadLoop();
|
||||
});
|
||||
}
|
||||
|
||||
void DispatchableThread::Terminate()
|
||||
{
|
||||
std::unique_lock lock(m_cb_mutex);
|
||||
|
||||
if (!m_terminate)
|
||||
{
|
||||
m_terminate = true;
|
||||
m_cv.notify_all();
|
||||
lock.unlock();
|
||||
m_thread.join();
|
||||
}
|
||||
else
|
||||
{
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void DispatchableThread::Dispatch(cb_t cb)
|
||||
{
|
||||
std::lock_guard lock(m_cb_mutex);
|
||||
|
||||
m_cb_list.emplace_back(std::move(cb));
|
||||
m_cv.notify_one();
|
||||
}
|
||||
|
||||
std::optional<DispatchableThread::cb_t> DispatchableThread::NextCallback()
|
||||
{
|
||||
if (m_terminate || m_cb_list.empty())
|
||||
return std::nullopt;
|
||||
|
||||
auto cb = std::move(m_cb_list.front());
|
||||
m_cb_list.pop_front();
|
||||
|
||||
return cb;
|
||||
}
|
||||
|
||||
void DispatchableThread::ThreadLoop()
|
||||
{
|
||||
while (!m_terminate)
|
||||
{
|
||||
std::unique_lock lock(m_cb_mutex);
|
||||
m_cv.wait(lock,
|
||||
[&]
|
||||
{
|
||||
return !m_cb_list.empty() || m_terminate;
|
||||
});
|
||||
|
||||
auto maybeCb = NextCallback();
|
||||
|
||||
lock.unlock();
|
||||
|
||||
if (maybeCb)
|
||||
(*maybeCb)();
|
||||
}
|
||||
}
|
37
src/ModMan/Utils/DispatchableThread.h
Normal file
37
src/ModMan/Utils/DispatchableThread.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
|
||||
class DispatchableThread
|
||||
{
|
||||
public:
|
||||
using cb_t = std::function<void()>;
|
||||
|
||||
DispatchableThread();
|
||||
~DispatchableThread();
|
||||
DispatchableThread(const DispatchableThread& other) = delete;
|
||||
DispatchableThread(DispatchableThread&& other) noexcept = default;
|
||||
DispatchableThread& operator=(const DispatchableThread& other) = delete;
|
||||
DispatchableThread& operator=(DispatchableThread&& other) noexcept = default;
|
||||
|
||||
void Start();
|
||||
void Terminate();
|
||||
|
||||
void Dispatch(cb_t cb);
|
||||
|
||||
private:
|
||||
std::optional<cb_t> NextCallback();
|
||||
void ThreadLoop();
|
||||
|
||||
std::mutex m_cb_mutex;
|
||||
std::deque<cb_t> m_cb_list;
|
||||
|
||||
std::condition_variable m_cv;
|
||||
std::thread m_thread;
|
||||
bool m_terminate;
|
||||
};
|
15
src/ModMan/Web/Binds/Binds.cpp
Normal file
15
src/ModMan/Web/Binds/Binds.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#include "Binds.h"
|
||||
|
||||
#include "UnlinkingBinds.h"
|
||||
#include "Web/Binds/DialogBinds.h"
|
||||
#include "ZoneBinds.h"
|
||||
|
||||
namespace ui
|
||||
{
|
||||
void RegisterAllBinds(webview::webview& wv)
|
||||
{
|
||||
RegisterDialogHandlerBinds(wv);
|
||||
RegisterUnlinkingBinds(wv);
|
||||
RegisterZoneBinds(wv);
|
||||
}
|
||||
} // namespace ui
|
8
src/ModMan/Web/Binds/Binds.h
Normal file
8
src/ModMan/Web/Binds/Binds.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "Web/WebViewLib.h"
|
||||
|
||||
namespace ui
|
||||
{
|
||||
void RegisterAllBinds(webview::webview& wv);
|
||||
}
|
93
src/ModMan/Web/Binds/UnlinkingBinds.cpp
Normal file
93
src/ModMan/Web/Binds/UnlinkingBinds.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#include "UnlinkingBinds.h"
|
||||
|
||||
#include "Context/ModManContext.h"
|
||||
#include "IObjWriter.h"
|
||||
#include "SearchPath/OutputPathFilesystem.h"
|
||||
#include "SearchPath/SearchPaths.h"
|
||||
#include "Utils/PathUtils.h"
|
||||
#include "Web/UiCommunication.h"
|
||||
|
||||
#include "Json/JsonExtension.h"
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace
|
||||
{
|
||||
class ZoneLoadedDto
|
||||
{
|
||||
public:
|
||||
std::string zoneName;
|
||||
std::string filePath;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneLoadedDto, zoneName, filePath);
|
||||
|
||||
class ZoneUnloadedDto
|
||||
{
|
||||
public:
|
||||
std::string zoneName;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneUnloadedDto, zoneName);
|
||||
|
||||
result::Expected<NoResult, std::string> UnlinkZoneInDbThread(const std::string& zoneName)
|
||||
{
|
||||
const auto& context = ModManContext::Get().m_fast_file;
|
||||
const auto existingZone = std::ranges::find_if(context.m_loaded_zones,
|
||||
[&zoneName](const std::unique_ptr<Zone>& zone)
|
||||
{
|
||||
return zone->m_name == zoneName;
|
||||
});
|
||||
|
||||
if (existingZone == context.m_loaded_zones.end())
|
||||
return result::Unexpected(std::format("No zone with name {} loaded", zoneName));
|
||||
|
||||
const auto& zone = *existingZone->get();
|
||||
|
||||
const auto* objWriter = IObjWriter::GetObjWriterForGame(zone.m_game_id);
|
||||
|
||||
const auto outputFolderPath = fs::path(utils::GetExecutablePath()).parent_path() / "zone_dump" / zoneName;
|
||||
const auto outputFolderPathStr = outputFolderPath.string();
|
||||
|
||||
OutputPathFilesystem outputFolderOutputPath(outputFolderPath);
|
||||
SearchPaths searchPaths;
|
||||
AssetDumpingContext dumpingContext(zone, outputFolderPathStr, outputFolderOutputPath, searchPaths);
|
||||
objWriter->DumpZone(dumpingContext);
|
||||
|
||||
return NoResult();
|
||||
}
|
||||
|
||||
void UnlinkZone(webview::webview& wv, std::string id, std::string zoneName) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety
|
||||
{
|
||||
ModManContext::Get().m_db_thread.Dispatch(
|
||||
[&wv, id, zoneName]
|
||||
{
|
||||
auto result = UnlinkZoneInDbThread(zoneName);
|
||||
|
||||
if (result)
|
||||
{
|
||||
con::debug("Unlinked zone \"{}\"", zoneName);
|
||||
ui::PromiseResolve(wv, id, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
con::warn("Failed to unlink zone \"{}\": {}", zoneName, result.error());
|
||||
ui::PromiseReject(wv, id, std::move(result).error());
|
||||
}
|
||||
});
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace ui
|
||||
{
|
||||
void RegisterUnlinkingBinds(webview::webview& wv)
|
||||
{
|
||||
BindAsync<std::string>(wv,
|
||||
"unlinkZone",
|
||||
[&wv](const std::string& id, std::string zoneName)
|
||||
{
|
||||
UnlinkZone(wv, id, std::move(zoneName));
|
||||
});
|
||||
}
|
||||
} // namespace ui
|
8
src/ModMan/Web/Binds/UnlinkingBinds.h
Normal file
8
src/ModMan/Web/Binds/UnlinkingBinds.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "Web/WebViewLib.h"
|
||||
|
||||
namespace ui
|
||||
{
|
||||
void RegisterUnlinkingBinds(webview::webview& wv);
|
||||
} // namespace ui
|
107
src/ModMan/Web/Binds/ZoneBinds.cpp
Normal file
107
src/ModMan/Web/Binds/ZoneBinds.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
#include "ZoneBinds.h"
|
||||
|
||||
#include "Context/ModManContext.h"
|
||||
#include "Web/UiCommunication.h"
|
||||
|
||||
#include "Json/JsonExtension.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
class ZoneLoadedDto
|
||||
{
|
||||
public:
|
||||
std::string zoneName;
|
||||
std::string filePath;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneLoadedDto, zoneName, filePath);
|
||||
|
||||
class ZoneUnloadedDto
|
||||
{
|
||||
public:
|
||||
std::string zoneName;
|
||||
};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneUnloadedDto, zoneName);
|
||||
|
||||
void LoadFastFile(webview::webview& wv, std::string id, std::string path) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety
|
||||
{
|
||||
ModManContext::Get().m_db_thread.Dispatch(
|
||||
[&wv, id, path]
|
||||
{
|
||||
auto maybeZone = ModManContext::Get().m_fast_file.LoadFastFile(path);
|
||||
|
||||
if (maybeZone)
|
||||
{
|
||||
ui::PromiseResolve(wv,
|
||||
id,
|
||||
ZoneLoadedDto{
|
||||
.zoneName = maybeZone.value()->m_name,
|
||||
.filePath = path,
|
||||
});
|
||||
con::debug("Loaded zone \"{}\"", maybeZone.value()->m_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
con::warn("Failed to load zone \"{}\": {}", path, maybeZone.error());
|
||||
ui::PromiseReject(wv, id, std::move(maybeZone).error());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void UnloadZone(webview::webview& wv, std::string id, std::string zoneName) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety
|
||||
{
|
||||
ModManContext::Get().m_db_thread.Dispatch(
|
||||
[&wv, id, zoneName]
|
||||
{
|
||||
auto result = ModManContext::Get().m_fast_file.UnloadZone(zoneName);
|
||||
if (result)
|
||||
{
|
||||
con::debug("Unloaded zone \"{}\"", zoneName);
|
||||
ui::PromiseResolve(wv, id, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
con::warn("Failed unloading zone {}: {}", zoneName, result.error());
|
||||
ui::PromiseReject(wv, id, std::move(result).error());
|
||||
}
|
||||
});
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace ui
|
||||
{
|
||||
void NotifyZoneLoaded(std::string zoneName, std::string fastFilePath)
|
||||
{
|
||||
const ZoneLoadedDto dto{
|
||||
.zoneName = std::move(zoneName),
|
||||
.filePath = std::move(fastFilePath),
|
||||
};
|
||||
Notify(*ModManContext::Get().m_main_webview, "zoneLoaded", dto);
|
||||
}
|
||||
|
||||
void NotifyZoneUnloaded(std::string zoneName)
|
||||
{
|
||||
const ZoneUnloadedDto dto{
|
||||
.zoneName = std::move(zoneName),
|
||||
};
|
||||
Notify(*ModManContext::Get().m_main_webview, "zoneUnloaded", dto);
|
||||
}
|
||||
|
||||
void RegisterZoneBinds(webview::webview& wv)
|
||||
{
|
||||
BindAsync<std::string>(wv,
|
||||
"loadFastFile",
|
||||
[&wv](const std::string& id, std::string path)
|
||||
{
|
||||
LoadFastFile(wv, id, std::move(path));
|
||||
});
|
||||
|
||||
BindAsync<std::string>(wv,
|
||||
"unloadZone",
|
||||
[&wv](const std::string& id, std::string zoneName)
|
||||
{
|
||||
UnloadZone(wv, id, std::move(zoneName));
|
||||
});
|
||||
}
|
||||
} // namespace ui
|
11
src/ModMan/Web/Binds/ZoneBinds.h
Normal file
11
src/ModMan/Web/Binds/ZoneBinds.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "Web/WebViewLib.h"
|
||||
|
||||
namespace ui
|
||||
{
|
||||
void NotifyZoneLoaded(std::string zoneName, std::string fastFilePath);
|
||||
void NotifyZoneUnloaded(std::string zoneName);
|
||||
|
||||
void RegisterZoneBinds(webview::webview& wv);
|
||||
} // namespace ui
|
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#define NOMINMAX
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push, 0)
|
||||
#else
|
||||
|
@@ -1,32 +1,38 @@
|
||||
#include "GitVersion.h"
|
||||
#include "Web/Binds/DialogBinds.h"
|
||||
#include "Context/ModManContext.h"
|
||||
#include "GitVersion.h"
|
||||
#include "ModManArgs.h"
|
||||
#include "Web/Binds/Binds.h"
|
||||
#include "Web/Platform/AssetHandler.h"
|
||||
#include "Web/UiCommunication.h"
|
||||
#include "Web/ViteAssets.h"
|
||||
#include "Web/WebViewLib.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
using namespace std::string_literals;
|
||||
using namespace PLATFORM_NAMESPACE;
|
||||
|
||||
namespace
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
std::optional<webview::webview> devToolWindow;
|
||||
|
||||
void RunDevToolsWindow()
|
||||
void SpawnDevToolsWindow()
|
||||
{
|
||||
con::debug("Creating dev tools window");
|
||||
|
||||
auto& context = ModManContext::Get();
|
||||
|
||||
try
|
||||
{
|
||||
auto& newWindow = devToolWindow.emplace(false, nullptr);
|
||||
context.m_dev_tools_webview = std::make_unique<webview::webview>(false, nullptr);
|
||||
auto& newWindow = *context.m_dev_tools_webview;
|
||||
|
||||
newWindow.set_title("Devtools");
|
||||
newWindow.set_size(640, 480, WEBVIEW_HINT_NONE);
|
||||
newWindow.set_size(480, 320, WEBVIEW_HINT_MIN);
|
||||
@@ -39,59 +45,44 @@ namespace
|
||||
}
|
||||
#endif
|
||||
|
||||
int RunMainWindow()
|
||||
int SpawnMainWindow()
|
||||
{
|
||||
con::debug("Creating main window");
|
||||
|
||||
auto& context = ModManContext::Get();
|
||||
try
|
||||
{
|
||||
webview::webview w(
|
||||
context.m_main_webview = std::make_unique<webview::webview>(
|
||||
#ifdef _DEBUG
|
||||
true,
|
||||
#else
|
||||
false,
|
||||
#endif
|
||||
nullptr);
|
||||
w.set_title("OpenAssetTools ModMan");
|
||||
w.set_size(1280, 640, WEBVIEW_HINT_NONE);
|
||||
w.set_size(480, 320, WEBVIEW_HINT_MIN);
|
||||
auto& newWindow = *context.m_main_webview;
|
||||
|
||||
ui::RegisterDialogHandlerBinds(w);
|
||||
newWindow.set_title("OpenAssetTools ModMan");
|
||||
newWindow.set_size(1280, 640, WEBVIEW_HINT_NONE);
|
||||
newWindow.set_size(480, 320, WEBVIEW_HINT_MIN);
|
||||
|
||||
// A binding that counts up or down and immediately returns the new value.
|
||||
ui::Bind<std::string, std::string>(w,
|
||||
"greet",
|
||||
[&w](std::string name) -> std::string
|
||||
{
|
||||
ui::Notify(w, "greeting", name);
|
||||
return std::format("Hello from C++ {}!", name);
|
||||
});
|
||||
|
||||
#if defined(WEBVIEW_PLATFORM_WINDOWS) && defined(WEBVIEW_EDGE)
|
||||
InstallAssetHandler(w);
|
||||
constexpr auto urlPrefix = URL_PREFIX;
|
||||
#elif defined(WEBVIEW_PLATFORM_LINUX) && defined(WEBVIEW_GTK)
|
||||
InstallAssetHandler(w);
|
||||
constexpr auto urlPrefix = URL_PREFIX;
|
||||
#else
|
||||
#error Unsupported platform
|
||||
#endif
|
||||
InstallAssetHandler(newWindow);
|
||||
ui::RegisterAllBinds(newWindow);
|
||||
|
||||
#ifdef _DEBUG
|
||||
w.navigate(VITE_DEV_SERVER ? std::format("http://localhost:{}", VITE_DEV_SERVER_PORT) : std::format("{}index.html", urlPrefix));
|
||||
newWindow.navigate(VITE_DEV_SERVER ? std::format("http://localhost:{}", VITE_DEV_SERVER_PORT) : std::format("{}index.html", URL_PREFIX));
|
||||
|
||||
if (VITE_DEV_SERVER)
|
||||
{
|
||||
w.dispatch(
|
||||
newWindow.dispatch(
|
||||
[]
|
||||
{
|
||||
RunDevToolsWindow();
|
||||
SpawnDevToolsWindow();
|
||||
});
|
||||
}
|
||||
#else
|
||||
w.navigate(std::format("{}index.html", urlPrefix));
|
||||
newWindow.navigate(std::format("{}index.html", URL_PREFIX));
|
||||
#endif
|
||||
w.run();
|
||||
newWindow.run();
|
||||
}
|
||||
catch (const webview::exception& e)
|
||||
{
|
||||
@@ -104,16 +95,50 @@ namespace
|
||||
} // namespace
|
||||
|
||||
#ifdef _WIN32
|
||||
#define MODMAN_ARGC __argc
|
||||
#define MODMAN_ARGV const_cast<const char**>(__argv)
|
||||
int WINAPI WinMain(HINSTANCE /*hInst*/, HINSTANCE /*hPrevInst*/, LPSTR /*lpCmdLine*/, int /*nCmdShow*/)
|
||||
{
|
||||
#else
|
||||
int main()
|
||||
{
|
||||
#define MODMAN_ARGC argc
|
||||
#define MODMAN_ARGV argv
|
||||
int main(int argc, const char** argv)
|
||||
#endif
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Attach console if possible on Windows for stdout/stderr in console
|
||||
if (AttachConsole(-1))
|
||||
{
|
||||
FILE* fDummy;
|
||||
(void)freopen_s(&fDummy, "CONOUT$", "w", stdout);
|
||||
(void)freopen_s(&fDummy, "CONOUT$", "w", stderr);
|
||||
(void)freopen_s(&fDummy, "CONIN$", "r", stdin);
|
||||
std::cout.clear();
|
||||
std::clog.clear();
|
||||
std::cerr.clear();
|
||||
std::cin.clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
g_set_prgname("OpenAssetTools-ModMan");
|
||||
g_set_application_name("OpenAssetTools ModMan");
|
||||
#endif
|
||||
|
||||
ModManArgs args;
|
||||
auto shouldContinue = true;
|
||||
if (!args.ParseArgs(MODMAN_ARGC, MODMAN_ARGV, shouldContinue))
|
||||
return false;
|
||||
|
||||
if (!shouldContinue)
|
||||
return true;
|
||||
|
||||
con::info("Starting ModMan " GIT_VERSION);
|
||||
|
||||
const auto result = RunMainWindow();
|
||||
ModManContext::Get().Startup();
|
||||
|
||||
const auto result = SpawnMainWindow();
|
||||
|
||||
ModManContext::Get().Destroy();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@@ -1,28 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import { onUnmounted, ref } from "vue";
|
||||
import { webviewBinds, webviewAddEventListener, webviewRemoveEventListener } from "./native";
|
||||
import { computed, ref } from "vue";
|
||||
import { webviewBinds } from "@/native";
|
||||
import { useZoneStore } from "@/stores/ZoneStore";
|
||||
import SpinningLoader from "@/components/SpinningLoader.vue";
|
||||
|
||||
const greetMsg = ref("");
|
||||
const lastPersonGreeted = ref("");
|
||||
const lastPath = ref("");
|
||||
const name = ref("");
|
||||
const zoneStore = useZoneStore();
|
||||
const loadingFastFile = ref(false);
|
||||
const unlinkingFastFile = ref(false);
|
||||
|
||||
async function greet() {
|
||||
greetMsg.value = await webviewBinds.greet(name.value);
|
||||
const performingAction = computed<boolean>(() => loadingFastFile.value || unlinkingFastFile.value);
|
||||
|
||||
async function openFastFileSelect() {
|
||||
return await webviewBinds.openFileDialog({ filters: [{ name: "Fastfiles", filter: "*.ff" }] });
|
||||
}
|
||||
|
||||
function onPersonGreeted(person: string) {
|
||||
lastPersonGreeted.value = person;
|
||||
async function onOpenFastFileClick() {
|
||||
if (performingAction.value) return;
|
||||
|
||||
const fastFilePath = await openFastFileSelect();
|
||||
if (!fastFilePath) return;
|
||||
|
||||
loadingFastFile.value = true;
|
||||
|
||||
webviewBinds
|
||||
.loadFastFile(fastFilePath)
|
||||
.catch((e: string) => {
|
||||
console.error("Failed to load fastfile:", e);
|
||||
})
|
||||
.finally(() => {
|
||||
loadingFastFile.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
async function onOpenFastfileClick() {
|
||||
lastPath.value =
|
||||
(await webviewBinds.openFileDialog({ filters: [{ name: "Fastfiles", filter: "*.ff" }] })) ?? "";
|
||||
async function onUnlinkFastFileClick() {
|
||||
if (performingAction.value) return;
|
||||
|
||||
const fastFilePath = await openFastFileSelect();
|
||||
if (!fastFilePath) return;
|
||||
|
||||
try {
|
||||
unlinkingFastFile.value = true;
|
||||
|
||||
let loadedZoneName: string;
|
||||
try {
|
||||
loadedZoneName = (await webviewBinds.loadFastFile(fastFilePath)).zoneName;
|
||||
} catch (e: unknown) {
|
||||
console.error("Failed to load fastfile:", e as string);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await webviewBinds.unlinkZone(loadedZoneName);
|
||||
} catch (e: unknown) {
|
||||
console.error("Failed to unlink fastfile:", e as string);
|
||||
return;
|
||||
} finally {
|
||||
webviewBinds.unloadZone(loadedZoneName);
|
||||
}
|
||||
} finally {
|
||||
unlinkingFastFile.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
webviewAddEventListener("greeting", onPersonGreeted);
|
||||
|
||||
onUnmounted(() => webviewRemoveEventListener("greeting", onPersonGreeted));
|
||||
function onUnloadClicked(zoneName: string) {
|
||||
webviewBinds.unloadZone(zoneName).catch((e: string) => {
|
||||
console.error("Failed to unload zone:", e);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -30,25 +74,47 @@ onUnmounted(() => webviewRemoveEventListener("greeting", onPersonGreeted));
|
||||
<h1>Welcome to ModMan</h1>
|
||||
<small>Nothing to see here yet, this is mainly for testing</small>
|
||||
|
||||
<form class="row" @submit.prevent="greet">
|
||||
<input id="greet-input" v-model="name" placeholder="Enter a name..." autocomplete="off" />
|
||||
<button type="submit">Greet</button>
|
||||
</form>
|
||||
<p>{{ greetMsg }}</p>
|
||||
<p>The last person greeted is: {{ lastPersonGreeted }}</p>
|
||||
<p>
|
||||
<button @click="onOpenFastfileClick">Open fastfile</button>
|
||||
<span>The last path: {{ lastPath }}</span>
|
||||
</p>
|
||||
<div class="actions">
|
||||
<button :disabled="performingAction" @click="onOpenFastFileClick">
|
||||
<SpinningLoader v-if="loadingFastFile" class="loading" />
|
||||
<span>Load fastfile</span>
|
||||
</button>
|
||||
<button :disabled="performingAction" @click="onUnlinkFastFileClick">
|
||||
<SpinningLoader v-if="unlinkingFastFile" class="loading" />
|
||||
<span>Unlink fastfile</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>Loaded zones:</h3>
|
||||
<div class="zone-list">
|
||||
<div v-for="zone in zoneStore.loadedZones" :key="zone" class="zone">
|
||||
<span>{{ zone }}</span>
|
||||
<button :disabled="performingAction" @click="onUnloadClicked(zone)">Unload</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.logo.vite:hover {
|
||||
filter: drop-shadow(0 0 2em #747bff);
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
column-gap: 0.5em;
|
||||
}
|
||||
|
||||
.logo.vue:hover {
|
||||
filter: drop-shadow(0 0 2em #249b73);
|
||||
.loading {
|
||||
margin-right: 0.2em;
|
||||
}
|
||||
|
||||
.zone-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 0.5em;
|
||||
}
|
||||
|
||||
.zone > button {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
</style>
|
||||
|
30
src/ModManUi/src/components/SpinningLoader.vue
Normal file
30
src/ModManUi/src/components/SpinningLoader.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<svg viewBox="0 0 100 100" class="loading-spinner">
|
||||
<circle class="loading-spinner-box" cx="50" cy="50" r="40" fill="transparent" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.loading-spinner {
|
||||
width: 0.8em;
|
||||
height: 0.8em;
|
||||
display: inline-block;
|
||||
|
||||
animation: rotation 1s infinite linear;
|
||||
|
||||
.loading-spinner-box {
|
||||
stroke: currentColor;
|
||||
stroke-width: 10;
|
||||
stroke-dasharray: 100% 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotation {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -71,13 +71,17 @@ button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
button:not(:disabled):hover {
|
||||
border-color: #396cd8;
|
||||
}
|
||||
button:active {
|
||||
button:not(:disabled):active {
|
||||
border-color: #396cd8;
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
@@ -103,7 +107,7 @@ button {
|
||||
color: #ffffff;
|
||||
background-color: #0f0f0f98;
|
||||
}
|
||||
button:active {
|
||||
button:not(:disabled):active {
|
||||
background-color: #0f0f0f69;
|
||||
}
|
||||
}
|
@@ -12,7 +12,7 @@ export interface SaveFileDialogDto {
|
||||
}
|
||||
|
||||
export interface DialogBinds {
|
||||
openFileDialog(options?: OpenFileDialogDto): string | null;
|
||||
saveFileDialog(options?: SaveFileDialogDto): string | null;
|
||||
folderSelectDialog(): string | null;
|
||||
openFileDialog(options?: OpenFileDialogDto): Promise<string | null>;
|
||||
saveFileDialog(options?: SaveFileDialogDto): Promise<string | null>;
|
||||
folderSelectDialog(): Promise<string | null>;
|
||||
}
|
||||
|
3
src/ModManUi/src/native/UnlinkingBinds.ts
Normal file
3
src/ModManUi/src/native/UnlinkingBinds.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface UnlinkingBinds {
|
||||
unlinkZone(zoneName: string): Promise<void>;
|
||||
}
|
18
src/ModManUi/src/native/ZoneBinds.ts
Normal file
18
src/ModManUi/src/native/ZoneBinds.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export interface ZoneLoadedDto {
|
||||
zoneName: string;
|
||||
filePath: string;
|
||||
}
|
||||
|
||||
export interface ZoneUnloadedDto {
|
||||
zoneName: string;
|
||||
}
|
||||
|
||||
export interface ZoneBinds {
|
||||
loadFastFile(path: string): Promise<ZoneLoadedDto>;
|
||||
unloadZone(zoneName: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ZoneEventMap {
|
||||
zoneLoaded: ZoneLoadedDto;
|
||||
zoneUnloaded: ZoneUnloadedDto;
|
||||
}
|
@@ -1,13 +1,10 @@
|
||||
import type { DialogBinds } from "./DialogBinds";
|
||||
import type { UnlinkingBinds } from "./UnlinkingBinds";
|
||||
import type { ZoneBinds, ZoneEventMap } from "./ZoneBinds";
|
||||
|
||||
export type NativeMethods = DialogBinds & UnlinkingBinds & ZoneBinds;
|
||||
|
||||
export type NativeMethods = {
|
||||
greet(name: string): Promise<string>;
|
||||
} & DialogBinds;
|
||||
|
||||
interface NativeEventMap {
|
||||
greeting: string;
|
||||
}
|
||||
type NativeEventMap = ZoneEventMap;
|
||||
|
||||
type WebViewExtensions = {
|
||||
webviewBinds: NativeMethods;
|
||||
|
20
src/ModManUi/src/stores/ZoneStore.ts
Normal file
20
src/ModManUi/src/stores/ZoneStore.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { readonly, ref } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import { webviewAddEventListener } from "@/native";
|
||||
|
||||
export const useZoneStore = defineStore("zone", () => {
|
||||
const loadedZones = ref<string[]>([]);
|
||||
|
||||
webviewAddEventListener("zoneLoaded", (dto) => {
|
||||
loadedZones.value.push(dto.zoneName);
|
||||
});
|
||||
|
||||
webviewAddEventListener("zoneUnloaded", (dto) => {
|
||||
const index = loadedZones.value.indexOf(dto.zoneName);
|
||||
if (index >= 0) {
|
||||
loadedZones.value.splice(index, 1);
|
||||
}
|
||||
});
|
||||
|
||||
return { loadedZones: readonly(loadedZones) };
|
||||
});
|
@@ -1,12 +0,0 @@
|
||||
import { ref, computed } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useCounterStore = defineStore("counter", () => {
|
||||
const count = ref(0);
|
||||
const doubleCount = computed(() => count.value * 2);
|
||||
function increment() {
|
||||
count.value++;
|
||||
}
|
||||
|
||||
return { count, doubleCount, increment };
|
||||
});
|
@@ -1,19 +1,17 @@
|
||||
#include "Unlinker.h"
|
||||
|
||||
#include "ContentLister/ContentPrinter.h"
|
||||
#include "ContentLister/ZoneDefWriter.h"
|
||||
#include "IObjLoader.h"
|
||||
#include "IObjWriter.h"
|
||||
#include "ObjWriting.h"
|
||||
#include "SearchPath/IWD.h"
|
||||
#include "SearchPath/OutputPathFilesystem.h"
|
||||
#include "SearchPath/SearchPathFilesystem.h"
|
||||
#include "SearchPath/SearchPaths.h"
|
||||
#include "UnlinkerArgs.h"
|
||||
#include "UnlinkerPaths.h"
|
||||
#include "Utils/ClassUtils.h"
|
||||
#include "Utils/Logging/Log.h"
|
||||
#include "Utils/ObjFileStream.h"
|
||||
#include "Zone/Definition/ZoneDefWriter.h"
|
||||
#include "ZoneLoading.h"
|
||||
|
||||
#include <cassert>
|
||||
@@ -21,7 +19,6 @@
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
#include <set>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@@ -54,12 +51,12 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
_NODISCARD bool ShouldLoadObj() const
|
||||
[[nodiscard]] bool ShouldLoadObj() const
|
||||
{
|
||||
return m_args.m_task != UnlinkerArgs::ProcessingTask::LIST && !m_args.m_skip_obj;
|
||||
}
|
||||
|
||||
bool WriteZoneDefinitionFile(const Zone& zone, const fs::path& zoneDefinitionFileFolder) const
|
||||
[[nodiscard]] bool WriteZoneDefinitionFile(const Zone& zone, const fs::path& zoneDefinitionFileFolder) const
|
||||
{
|
||||
auto zoneDefinitionFilePath(zoneDefinitionFileFolder);
|
||||
zoneDefinitionFilePath.append(zone.m_name);
|
||||
@@ -73,7 +70,7 @@ private:
|
||||
}
|
||||
|
||||
const auto* zoneDefWriter = IZoneDefWriter::GetZoneDefWriterForGame(zone.m_game_id);
|
||||
zoneDefWriter->WriteZoneDef(zoneDefinitionFile, m_args, zone);
|
||||
zoneDefWriter->WriteZoneDef(zoneDefinitionFile, zone, m_args.m_use_gdt);
|
||||
|
||||
zoneDefinitionFile.close();
|
||||
|
||||
@@ -230,13 +227,15 @@ private:
|
||||
auto absoluteZoneDirectory = absolute(std::filesystem::path(zonePath).remove_filename()).string();
|
||||
|
||||
auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory);
|
||||
auto zone = ZoneLoading::LoadZone(zonePath);
|
||||
if (zone == nullptr)
|
||||
auto maybeZone = ZoneLoading::LoadZone(zonePath);
|
||||
if (!maybeZone)
|
||||
{
|
||||
con::error("Failed to load zone \"{}\".", zonePath);
|
||||
con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error());
|
||||
return false;
|
||||
}
|
||||
|
||||
auto zone = std::move(*maybeZone);
|
||||
|
||||
con::debug("Loaded zone \"{}\"", zone->m_name);
|
||||
|
||||
if (ShouldLoadObj())
|
||||
@@ -290,16 +289,16 @@ private:
|
||||
|
||||
auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory);
|
||||
|
||||
std::string zoneName;
|
||||
auto zone = ZoneLoading::LoadZone(zonePath);
|
||||
if (zone == nullptr)
|
||||
auto maybeZone = ZoneLoading::LoadZone(zonePath);
|
||||
if (!maybeZone)
|
||||
{
|
||||
con::error("Failed to load zone \"{}\".", zonePath);
|
||||
con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error());
|
||||
return false;
|
||||
}
|
||||
|
||||
zoneName = zone->m_name;
|
||||
con::debug("Loaded zone \"{}\"", zoneName);
|
||||
auto zone = std::move(*maybeZone);
|
||||
|
||||
con::debug("Loaded zone \"{}\"", zone->m_name);
|
||||
|
||||
const auto* objLoader = IObjLoader::GetObjLoaderForGame(zone->m_game_id);
|
||||
if (ShouldLoadObj())
|
||||
@@ -311,6 +310,8 @@ private:
|
||||
if (ShouldLoadObj())
|
||||
objLoader->UnloadContainersOfZone(*zone);
|
||||
|
||||
// Copy zone name for using it after freeing the zone
|
||||
std::string zoneName = zone->m_name;
|
||||
zone.reset();
|
||||
con::debug("Unloaded zone \"{}\"", zoneName);
|
||||
}
|
||||
|
185
src/Utils/Utils/Result.h
Normal file
185
src/Utils/Utils/Result.h
Normal file
@@ -0,0 +1,185 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
|
||||
using NoResult = std::monostate;
|
||||
|
||||
// Can be replaced by std::expected with c++23
|
||||
namespace result
|
||||
{
|
||||
template<typename TError> class Unexpected
|
||||
{
|
||||
public:
|
||||
Unexpected(TError result)
|
||||
: m_data(std::move(result))
|
||||
{
|
||||
}
|
||||
|
||||
constexpr std::add_lvalue_reference_t<TError> value() &
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
constexpr std::add_const_t<std::add_lvalue_reference_t<TError>> value() const&
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
constexpr std::add_rvalue_reference_t<TError> value() &&
|
||||
{
|
||||
return std::move(m_data);
|
||||
}
|
||||
|
||||
constexpr std::add_const_t<std::add_rvalue_reference_t<TError>> value() const&&
|
||||
{
|
||||
return std::move(m_data);
|
||||
}
|
||||
|
||||
constexpr std::add_lvalue_reference_t<TError> operator*() &
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
constexpr std::add_const_t<std::add_lvalue_reference_t<TError>> operator*() const&
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
constexpr std::add_rvalue_reference_t<TError> operator*() &&
|
||||
{
|
||||
return std::move(m_data);
|
||||
}
|
||||
|
||||
constexpr std::add_const_t<std::add_rvalue_reference_t<TError>> operator*() const&&
|
||||
{
|
||||
return std::move(m_data);
|
||||
}
|
||||
|
||||
constexpr std::add_pointer_t<TError> operator->()
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
constexpr std::add_const_t<std::add_pointer_t<TError>> operator->() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
private:
|
||||
TError m_data;
|
||||
};
|
||||
|
||||
template<typename TResult, typename TError> class Expected
|
||||
{
|
||||
public:
|
||||
Expected(TResult result)
|
||||
: m_data(std::variant<TResult, TError>(std::in_place_index<0>, std::move(result)))
|
||||
{
|
||||
}
|
||||
|
||||
Expected(Unexpected<TError> unexpected)
|
||||
: m_data(std::variant<TResult, TError>(std::in_place_index<1>, std::move(*unexpected)))
|
||||
{
|
||||
}
|
||||
|
||||
constexpr operator bool() const noexcept
|
||||
{
|
||||
return m_data.index() == 0;
|
||||
}
|
||||
|
||||
constexpr bool has_value() const noexcept
|
||||
{
|
||||
return m_data.index() == 0;
|
||||
}
|
||||
|
||||
constexpr std::add_lvalue_reference_t<TResult> value() &
|
||||
{
|
||||
return std::get<0>(m_data);
|
||||
}
|
||||
|
||||
constexpr std::add_const_t<std::add_lvalue_reference_t<TResult>> value() const&
|
||||
{
|
||||
return std::get<0>(m_data);
|
||||
}
|
||||
|
||||
constexpr std::add_rvalue_reference_t<TResult> value() &&
|
||||
{
|
||||
return std::move(std::get<0>(m_data));
|
||||
}
|
||||
|
||||
constexpr std::add_const_t<std::add_rvalue_reference_t<TResult>> value() const&&
|
||||
{
|
||||
return std::move(std::get<0>(m_data));
|
||||
}
|
||||
|
||||
constexpr std::add_lvalue_reference_t<TResult> operator*() &
|
||||
{
|
||||
return std::get<0>(m_data);
|
||||
}
|
||||
|
||||
constexpr std::add_const_t<std::add_lvalue_reference_t<TResult>> operator*() const&
|
||||
{
|
||||
return std::get<0>(m_data);
|
||||
}
|
||||
|
||||
constexpr std::add_rvalue_reference_t<TResult> operator*() &&
|
||||
{
|
||||
return std::move(std::get<0>(m_data));
|
||||
}
|
||||
|
||||
constexpr std::add_const_t<std::add_rvalue_reference_t<TResult>> operator*() const&&
|
||||
{
|
||||
return std::move(std::get<0>(m_data));
|
||||
}
|
||||
|
||||
constexpr std::add_pointer_t<TResult> operator->()
|
||||
{
|
||||
return std::get<0>(m_data);
|
||||
}
|
||||
|
||||
constexpr std::add_const_t<std::add_pointer_t<TResult>> operator->() const
|
||||
{
|
||||
return std::get<0>(m_data);
|
||||
}
|
||||
|
||||
constexpr std::add_lvalue_reference_t<TError> error() &
|
||||
{
|
||||
return std::get<1>(m_data);
|
||||
}
|
||||
|
||||
constexpr std::add_const_t<std::add_lvalue_reference_t<TError>> error() const&
|
||||
{
|
||||
return std::get<1>(m_data);
|
||||
}
|
||||
|
||||
constexpr std::add_rvalue_reference_t<TError> error() &&
|
||||
{
|
||||
return std::move(std::get<1>(m_data));
|
||||
}
|
||||
|
||||
constexpr std::add_const_t<std::add_rvalue_reference_t<TError>> error() const&&
|
||||
{
|
||||
return std::move(std::get<1>(m_data));
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Expected(std::variant<TResult, TError> data)
|
||||
: m_data(std::move(data))
|
||||
{
|
||||
}
|
||||
|
||||
std::variant<TResult, TError> m_data;
|
||||
};
|
||||
|
||||
#define ENSURE_RESULT_VAR(var) \
|
||||
if (!(var)) \
|
||||
return (var);
|
||||
#define ENSURE_RESULT(expr) \
|
||||
{ \
|
||||
const auto result = (expr); \
|
||||
if (!result) \
|
||||
return result; \
|
||||
}
|
||||
|
||||
} // namespace result
|
@@ -6,9 +6,9 @@
|
||||
|
||||
using namespace IW3;
|
||||
|
||||
void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const {}
|
||||
void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const {}
|
||||
|
||||
void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const
|
||||
void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const
|
||||
{
|
||||
const auto* pools = dynamic_cast<GameAssetPoolIW3*>(zone.m_pools.get());
|
||||
|
@@ -1,13 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "ContentLister/ZoneDefWriter.h"
|
||||
#include "Zone/Definition/ZoneDefWriter.h"
|
||||
|
||||
namespace IW3
|
||||
{
|
||||
class ZoneDefWriter final : public AbstractZoneDefWriter
|
||||
{
|
||||
protected:
|
||||
void WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override;
|
||||
void WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override;
|
||||
void WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const override;
|
||||
void WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const override;
|
||||
};
|
||||
} // namespace IW3
|
@@ -6,9 +6,9 @@
|
||||
|
||||
using namespace IW4;
|
||||
|
||||
void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const {}
|
||||
void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const {}
|
||||
|
||||
void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const
|
||||
void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const
|
||||
{
|
||||
const auto* pools = dynamic_cast<GameAssetPoolIW4*>(zone.m_pools.get());
|
||||
|
@@ -1,13 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "ContentLister/ZoneDefWriter.h"
|
||||
#include "Zone/Definition/ZoneDefWriter.h"
|
||||
|
||||
namespace IW4
|
||||
{
|
||||
class ZoneDefWriter final : public AbstractZoneDefWriter
|
||||
{
|
||||
protected:
|
||||
void WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override;
|
||||
void WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override;
|
||||
void WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const override;
|
||||
void WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const override;
|
||||
};
|
||||
} // namespace IW4
|
@@ -6,9 +6,9 @@
|
||||
|
||||
using namespace IW5;
|
||||
|
||||
void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const {}
|
||||
void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const {}
|
||||
|
||||
void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const
|
||||
void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const
|
||||
{
|
||||
const auto* pools = dynamic_cast<GameAssetPoolIW5*>(zone.m_pools.get());
|
||||
|
@@ -1,13 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "ContentLister/ZoneDefWriter.h"
|
||||
#include "Zone/Definition/ZoneDefWriter.h"
|
||||
|
||||
namespace IW5
|
||||
{
|
||||
class ZoneDefWriter final : public AbstractZoneDefWriter
|
||||
{
|
||||
protected:
|
||||
void WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override;
|
||||
void WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override;
|
||||
void WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const override;
|
||||
void WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const override;
|
||||
};
|
||||
} // namespace IW5
|
@@ -6,9 +6,9 @@
|
||||
|
||||
using namespace T5;
|
||||
|
||||
void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const {}
|
||||
void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const {}
|
||||
|
||||
void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const
|
||||
void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const
|
||||
{
|
||||
const auto* pools = dynamic_cast<GameAssetPoolT5*>(zone.m_pools.get());
|
||||
|
@@ -1,13 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "ContentLister/ZoneDefWriter.h"
|
||||
#include "Zone/Definition/ZoneDefWriter.h"
|
||||
|
||||
namespace T5
|
||||
{
|
||||
class ZoneDefWriter final : public AbstractZoneDefWriter
|
||||
{
|
||||
protected:
|
||||
void WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override;
|
||||
void WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override;
|
||||
void WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const override;
|
||||
void WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const override;
|
||||
};
|
||||
} // namespace T5
|
@@ -48,7 +48,7 @@ namespace
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const
|
||||
void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const
|
||||
{
|
||||
const auto* assetPoolT6 = dynamic_cast<GameAssetPoolT6*>(zone.m_pools.get());
|
||||
if (assetPoolT6 && !assetPoolT6->m_key_value_pairs->m_asset_lookup.empty())
|
||||
@@ -64,7 +64,7 @@ void ZoneDefWriter::WriteMetaData(ZoneDefinitionOutputStream& stream, const Unli
|
||||
}
|
||||
}
|
||||
|
||||
void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const
|
||||
void ZoneDefWriter::WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const
|
||||
{
|
||||
const auto* pools = dynamic_cast<GameAssetPoolT6*>(zone.m_pools.get());
|
||||
|
@@ -1,13 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "ContentLister/ZoneDefWriter.h"
|
||||
#include "Zone/Definition/ZoneDefWriter.h"
|
||||
|
||||
namespace T6
|
||||
{
|
||||
class ZoneDefWriter final : public AbstractZoneDefWriter
|
||||
{
|
||||
protected:
|
||||
void WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override;
|
||||
void WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const override;
|
||||
void WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const override;
|
||||
void WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const override;
|
||||
};
|
||||
} // namespace T6
|
@@ -1,10 +1,10 @@
|
||||
#include "ZoneDefWriter.h"
|
||||
|
||||
#include "Game/IW3/ZoneDefWriterIW3.h"
|
||||
#include "Game/IW4/ZoneDefWriterIW4.h"
|
||||
#include "Game/IW5/ZoneDefWriterIW5.h"
|
||||
#include "Game/T5/ZoneDefWriterT5.h"
|
||||
#include "Game/T6/ZoneDefWriterT6.h"
|
||||
#include "Game/IW3/Zone/Definition/ZoneDefWriterIW3.h"
|
||||
#include "Game/IW4/Zone/Definition/ZoneDefWriterIW4.h"
|
||||
#include "Game/IW5/Zone/Definition/ZoneDefWriterIW5.h"
|
||||
#include "Game/T5/Zone/Definition/ZoneDefWriterT5.h"
|
||||
#include "Game/T6/Zone/Definition/ZoneDefWriterT6.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
@@ -25,7 +25,7 @@ const IZoneDefWriter* IZoneDefWriter::GetZoneDefWriterForGame(GameId game)
|
||||
return result;
|
||||
}
|
||||
|
||||
void AbstractZoneDefWriter::WriteZoneDef(std::ostream& stream, const UnlinkerArgs& args, const Zone& zone) const
|
||||
void AbstractZoneDefWriter::WriteZoneDef(std::ostream& stream, const Zone& zone, const bool useGdt) const
|
||||
{
|
||||
ZoneDefinitionOutputStream out(stream);
|
||||
const auto* game = IGame::GetGameById(zone.m_game_id);
|
||||
@@ -34,13 +34,13 @@ void AbstractZoneDefWriter::WriteZoneDef(std::ostream& stream, const UnlinkerArg
|
||||
out.WriteMetaData(META_DATA_KEY_GAME, game->GetShortName());
|
||||
out.EmptyLine();
|
||||
|
||||
if (args.m_use_gdt)
|
||||
if (useGdt)
|
||||
{
|
||||
out.WriteComment("Load asset gdt files");
|
||||
out.WriteMetaData(META_DATA_KEY_GDT, zone.m_name);
|
||||
out.EmptyLine();
|
||||
}
|
||||
|
||||
WriteMetaData(out, args, zone);
|
||||
WriteContent(out, args, zone);
|
||||
WriteMetaData(out, zone);
|
||||
WriteContent(out, zone);
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "UnlinkerArgs.h"
|
||||
#include "Zone/Definition/ZoneDefinitionStream.h"
|
||||
#include "Zone/Zone.h"
|
||||
|
||||
class IZoneDefWriter
|
||||
{
|
||||
@@ -13,7 +13,7 @@ public:
|
||||
IZoneDefWriter& operator=(const IZoneDefWriter& other) = default;
|
||||
IZoneDefWriter& operator=(IZoneDefWriter&& other) noexcept = default;
|
||||
|
||||
virtual void WriteZoneDef(std::ostream& stream, const UnlinkerArgs& args, const Zone& zone) const = 0;
|
||||
virtual void WriteZoneDef(std::ostream& stream, const Zone& zone, bool useGdt) const = 0;
|
||||
|
||||
static const IZoneDefWriter* GetZoneDefWriterForGame(GameId game);
|
||||
};
|
||||
@@ -24,9 +24,9 @@ protected:
|
||||
static constexpr auto META_DATA_KEY_GAME = "game";
|
||||
static constexpr auto META_DATA_KEY_GDT = "gdt";
|
||||
|
||||
virtual void WriteMetaData(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const = 0;
|
||||
virtual void WriteContent(ZoneDefinitionOutputStream& stream, const UnlinkerArgs& args, const Zone& zone) const = 0;
|
||||
virtual void WriteMetaData(ZoneDefinitionOutputStream& stream, const Zone& zone) const = 0;
|
||||
virtual void WriteContent(ZoneDefinitionOutputStream& stream, const Zone& zone) const = 0;
|
||||
|
||||
public:
|
||||
void WriteZoneDef(std::ostream& stream, const UnlinkerArgs& args, const Zone& zone) const override;
|
||||
void WriteZoneDef(std::ostream& stream, const Zone& zone, bool useGdt) const override;
|
||||
};
|
@@ -8,7 +8,8 @@ Zone::Zone(std::string name, const zone_priority_t priority, GameId gameId)
|
||||
m_language(GameLanguage::LANGUAGE_NONE),
|
||||
m_game_id(gameId),
|
||||
m_pools(ZoneAssetPools::CreateForGame(gameId, this, priority)),
|
||||
m_memory(std::make_unique<ZoneMemory>())
|
||||
m_memory(std::make_unique<ZoneMemory>()),
|
||||
m_registered(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "Loading/IZoneLoaderFactory.h"
|
||||
#include "Loading/ZoneLoader.h"
|
||||
#include "Utils/Logging/Log.h"
|
||||
#include "Utils/ObjFileStream.h"
|
||||
|
||||
#include <filesystem>
|
||||
@@ -12,24 +11,18 @@
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
std::unique_ptr<Zone> ZoneLoading::LoadZone(const std::string& path)
|
||||
result::Expected<std::unique_ptr<Zone>, std::string> ZoneLoading::LoadZone(const std::string& path)
|
||||
{
|
||||
auto zoneName = fs::path(path).filename().replace_extension().string();
|
||||
std::ifstream file(path, std::fstream::in | std::fstream::binary);
|
||||
|
||||
if (!file.is_open())
|
||||
{
|
||||
con::error("Could not open file '{}'.", path);
|
||||
return nullptr;
|
||||
}
|
||||
return result::Unexpected(std::format("Could not open file '{}'.", path));
|
||||
|
||||
ZoneHeader header{};
|
||||
file.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
if (file.gcount() != sizeof(header))
|
||||
{
|
||||
con::error("Failed to read zone header from file '{}'.", path);
|
||||
return nullptr;
|
||||
}
|
||||
return result::Unexpected(std::format("Failed to read zone header from file '{}'.", path));
|
||||
|
||||
std::unique_ptr<ZoneLoader> zoneLoader;
|
||||
for (auto game = 0u; game < static_cast<unsigned>(GameId::COUNT); game++)
|
||||
@@ -42,10 +35,7 @@ std::unique_ptr<Zone> ZoneLoading::LoadZone(const std::string& path)
|
||||
}
|
||||
|
||||
if (!zoneLoader)
|
||||
{
|
||||
con::error("Could not create factory for zone '{}'.", zoneName);
|
||||
return nullptr;
|
||||
}
|
||||
return result::Unexpected(std::format("Could not create factory for zone '{}'.", zoneName));
|
||||
|
||||
auto loadedZone = zoneLoader->LoadZone(file);
|
||||
|
||||
|
@@ -1,4 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "Utils/Result.h"
|
||||
#include "Zone/Zone.h"
|
||||
|
||||
#include <string>
|
||||
@@ -6,5 +8,5 @@
|
||||
class ZoneLoading
|
||||
{
|
||||
public:
|
||||
static std::unique_ptr<Zone> LoadZone(const std::string& path);
|
||||
static result::Expected<std::unique_ptr<Zone>, std::string> LoadZone(const std::string& path);
|
||||
};
|
||||
|
Reference in New Issue
Block a user