mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2025-12-03 17:57:48 +00:00
feat: show loading progress in modman
This commit is contained in:
@@ -5,5 +5,8 @@
|
|||||||
class ProgressCallback
|
class ProgressCallback
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
ProgressCallback() = default;
|
||||||
|
virtual ~ProgressCallback() = default;
|
||||||
|
|
||||||
virtual void OnProgress(size_t current, size_t total) = 0;
|
virtual void OnProgress(size_t current, size_t total) = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,40 @@
|
|||||||
#include "Web/UiCommunication.h"
|
#include "Web/UiCommunication.h"
|
||||||
#include "ZoneLoading.h"
|
#include "ZoneLoading.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
constexpr double MIN_PROGRESS_TO_REPORT = 0.005;
|
||||||
|
|
||||||
|
class EventProgressReporter : public ProgressCallback
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit EventProgressReporter(std::string zoneName)
|
||||||
|
: m_zone_name(std::move(zoneName)),
|
||||||
|
m_last_progress(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnProgress(const size_t current, const size_t total) override
|
||||||
|
{
|
||||||
|
const double percentage = static_cast<double>(current) / static_cast<double>(total);
|
||||||
|
|
||||||
|
if (percentage - m_last_progress >= MIN_PROGRESS_TO_REPORT)
|
||||||
|
{
|
||||||
|
m_last_progress = percentage;
|
||||||
|
ui::NotifyZoneLoadProgress(m_zone_name, percentage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_zone_name;
|
||||||
|
double m_last_progress;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void FastFileContext::Destroy()
|
void FastFileContext::Destroy()
|
||||||
{
|
{
|
||||||
// Unload all zones
|
// Unload all zones
|
||||||
@@ -12,7 +46,7 @@ void FastFileContext::Destroy()
|
|||||||
|
|
||||||
result::Expected<Zone*, std::string> FastFileContext::LoadFastFile(const std::string& path)
|
result::Expected<Zone*, std::string> FastFileContext::LoadFastFile(const std::string& path)
|
||||||
{
|
{
|
||||||
auto zone = ZoneLoading::LoadZone(path, std::nullopt);
|
auto zone = ZoneLoading::LoadZone(path, std::make_unique<EventProgressReporter>(fs::path(path).filename().replace_extension().string()));
|
||||||
if (!zone)
|
if (!zone)
|
||||||
return result::Unexpected(std::move(zone.error()));
|
return result::Unexpected(std::move(zone.error()));
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,15 @@
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
class ZoneLoadProgressDto
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string zoneName;
|
||||||
|
double percentage;
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneLoadProgressDto, zoneName, percentage);
|
||||||
|
|
||||||
class ZoneLoadedDto
|
class ZoneLoadedDto
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -71,6 +80,15 @@ namespace
|
|||||||
|
|
||||||
namespace ui
|
namespace ui
|
||||||
{
|
{
|
||||||
|
void NotifyZoneLoadProgress(std::string zoneName, const double percentage)
|
||||||
|
{
|
||||||
|
const ZoneLoadProgressDto dto{
|
||||||
|
.zoneName = std::move(zoneName),
|
||||||
|
.percentage = percentage,
|
||||||
|
};
|
||||||
|
Notify(*ModManContext::Get().m_main_webview, "zoneLoadProgress", dto);
|
||||||
|
}
|
||||||
|
|
||||||
void NotifyZoneLoaded(std::string zoneName, std::string fastFilePath)
|
void NotifyZoneLoaded(std::string zoneName, std::string fastFilePath)
|
||||||
{
|
{
|
||||||
const ZoneLoadedDto dto{
|
const ZoneLoadedDto dto{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
namespace ui
|
namespace ui
|
||||||
{
|
{
|
||||||
|
void NotifyZoneLoadProgress(std::string zoneName, double percentage);
|
||||||
void NotifyZoneLoaded(std::string zoneName, std::string fastFilePath);
|
void NotifyZoneLoaded(std::string zoneName, std::string fastFilePath);
|
||||||
void NotifyZoneUnloaded(std::string zoneName);
|
void NotifyZoneUnloaded(std::string zoneName);
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { webviewBinds } from "@/native";
|
import { webviewAddEventListener, webviewBinds } from "@/native";
|
||||||
import { useZoneStore } from "@/stores/ZoneStore";
|
import { useZoneStore } from "@/stores/ZoneStore";
|
||||||
import SpinningLoader from "@/components/SpinningLoader.vue";
|
import SpinningLoader from "@/components/SpinningLoader.vue";
|
||||||
|
|
||||||
const zoneStore = useZoneStore();
|
const zoneStore = useZoneStore();
|
||||||
const loadingFastFile = ref(false);
|
const loadingFastFile = ref(false);
|
||||||
const unlinkingFastFile = ref(false);
|
const unlinkingFastFile = ref(false);
|
||||||
|
const lastPercentage = ref<number>(0);
|
||||||
|
|
||||||
const performingAction = computed<boolean>(() => loadingFastFile.value || unlinkingFastFile.value);
|
const performingAction = computed<boolean>(() => loadingFastFile.value || unlinkingFastFile.value);
|
||||||
|
const progressBarWidth = computed<string>(() => `${lastPercentage.value * 100}%`);
|
||||||
|
|
||||||
async function openFastFileSelect() {
|
async function openFastFileSelect() {
|
||||||
return await webviewBinds.openFileDialog({ filters: [{ name: "Fastfiles", filter: "*.ff" }] });
|
return await webviewBinds.openFileDialog({ filters: [{ name: "Fastfiles", filter: "*.ff" }] });
|
||||||
@@ -21,6 +23,7 @@ async function onOpenFastFileClick() {
|
|||||||
if (!fastFilePath) return;
|
if (!fastFilePath) return;
|
||||||
|
|
||||||
loadingFastFile.value = true;
|
loadingFastFile.value = true;
|
||||||
|
lastPercentage.value = 0;
|
||||||
|
|
||||||
webviewBinds
|
webviewBinds
|
||||||
.loadFastFile(fastFilePath)
|
.loadFastFile(fastFilePath)
|
||||||
@@ -29,6 +32,7 @@ async function onOpenFastFileClick() {
|
|||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
loadingFastFile.value = false;
|
loadingFastFile.value = false;
|
||||||
|
lastPercentage.value = 1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +47,7 @@ async function onUnlinkFastFileClick() {
|
|||||||
|
|
||||||
let loadedZoneName: string;
|
let loadedZoneName: string;
|
||||||
try {
|
try {
|
||||||
|
lastPercentage.value = 0;
|
||||||
loadedZoneName = (await webviewBinds.loadFastFile(fastFilePath)).zoneName;
|
loadedZoneName = (await webviewBinds.loadFastFile(fastFilePath)).zoneName;
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
console.error("Failed to load fastfile:", e as string);
|
console.error("Failed to load fastfile:", e as string);
|
||||||
@@ -50,6 +55,7 @@ async function onUnlinkFastFileClick() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
lastPercentage.value = 0;
|
||||||
await webviewBinds.unlinkZone(loadedZoneName);
|
await webviewBinds.unlinkZone(loadedZoneName);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
console.error("Failed to unlink fastfile:", e as string);
|
console.error("Failed to unlink fastfile:", e as string);
|
||||||
@@ -59,6 +65,7 @@ async function onUnlinkFastFileClick() {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
unlinkingFastFile.value = false;
|
unlinkingFastFile.value = false;
|
||||||
|
lastPercentage.value = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +74,11 @@ function onUnloadClicked(zoneName: string) {
|
|||||||
console.error("Failed to unload zone:", e);
|
console.error("Failed to unload zone:", e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
webviewAddEventListener("zoneLoadProgress", (dto) => {
|
||||||
|
console.log(dto);
|
||||||
|
lastPercentage.value = dto.percentage;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -94,10 +106,18 @@ function onUnloadClicked(zoneName: string) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="progressbar-wrapper">
|
||||||
|
<div
|
||||||
|
class="progressbar"
|
||||||
|
:class="{ visible: performingAction }"
|
||||||
|
:style="{ width: progressBarWidth }"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="scss">
|
||||||
.actions {
|
.actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -117,4 +137,24 @@ function onUnloadClicked(zoneName: string) {
|
|||||||
.zone > button {
|
.zone > button {
|
||||||
margin-left: 0.5em;
|
margin-left: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progressbar-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 0.35rem 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progressbar {
|
||||||
|
opacity: 0;
|
||||||
|
height: 0.4rem;
|
||||||
|
border-radius: 2.5rem;
|
||||||
|
background-color: #b9772c;
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -14,13 +14,24 @@
|
|||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-top: 10vh;
|
padding-top: 10vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
position: relative;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: start;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
export interface ZoneLoadProgressDto {
|
||||||
|
zoneName: string;
|
||||||
|
percentage: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ZoneLoadedDto {
|
export interface ZoneLoadedDto {
|
||||||
zoneName: string;
|
zoneName: string;
|
||||||
filePath: string;
|
filePath: string;
|
||||||
@@ -13,6 +18,7 @@ export interface ZoneBinds {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ZoneEventMap {
|
export interface ZoneEventMap {
|
||||||
|
zoneLoadProgress: ZoneLoadProgressDto;
|
||||||
zoneLoaded: ZoneLoadedDto;
|
zoneLoaded: ZoneLoadedDto;
|
||||||
zoneUnloaded: ZoneUnloadedDto;
|
zoneUnloaded: ZoneUnloadedDto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,8 +60,6 @@ namespace
|
|||||||
m_block_shift(pointerBitCount - blockBitCount),
|
m_block_shift(pointerBitCount - blockBitCount),
|
||||||
m_offset_mask(std::numeric_limits<uintptr_t>::max() >> (sizeof(uintptr_t) * 8 - (pointerBitCount - blockBitCount))),
|
m_offset_mask(std::numeric_limits<uintptr_t>::max() >> (sizeof(uintptr_t) * 8 - (pointerBitCount - blockBitCount))),
|
||||||
m_last_fill_size(0),
|
m_last_fill_size(0),
|
||||||
m_has_progress_callback(progressCallback.has_value()),
|
|
||||||
m_progress_callback(std::move(progressCallback).value_or(nullptr)),
|
|
||||||
m_progress_current_size(0uz),
|
m_progress_current_size(0uz),
|
||||||
m_progress_total_size(0uz)
|
m_progress_total_size(0uz)
|
||||||
{
|
{
|
||||||
@@ -74,7 +72,12 @@ namespace
|
|||||||
|
|
||||||
m_insert_block = blocks[insertBlock];
|
m_insert_block = blocks[insertBlock];
|
||||||
|
|
||||||
m_progress_total_size = CalculateTotalSize();
|
if (progressCallback)
|
||||||
|
{
|
||||||
|
m_has_progress_callback = true;
|
||||||
|
m_progress_callback = *std::move(progressCallback);
|
||||||
|
m_progress_total_size = CalculateTotalSize();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] unsigned GetPointerBitCount() const override
|
[[nodiscard]] unsigned GetPointerBitCount() const override
|
||||||
@@ -452,7 +455,8 @@ namespace
|
|||||||
{
|
{
|
||||||
m_block_offsets[block.m_index] += size;
|
m_block_offsets[block.m_index] += size;
|
||||||
|
|
||||||
if (m_has_progress_callback)
|
// We cannot know the full size of the temp block
|
||||||
|
if (m_has_progress_callback && block.m_type != XBlockType::BLOCK_TYPE_TEMP)
|
||||||
{
|
{
|
||||||
m_progress_current_size += size;
|
m_progress_current_size += size;
|
||||||
m_progress_callback->OnProgress(m_progress_current_size, m_progress_total_size);
|
m_progress_callback->OnProgress(m_progress_current_size, m_progress_total_size);
|
||||||
@@ -473,7 +477,11 @@ namespace
|
|||||||
size_t result = 0uz;
|
size_t result = 0uz;
|
||||||
|
|
||||||
for (const auto& block : m_blocks)
|
for (const auto& block : m_blocks)
|
||||||
result += block->m_buffer_size;
|
{
|
||||||
|
// We cannot know the full size of the temp block
|
||||||
|
if (block->m_type != XBlockType::BLOCK_TYPE_TEMP)
|
||||||
|
result += block->m_buffer_size;
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,10 +28,11 @@ result::Expected<std::unique_ptr<Zone>, std::string> ZoneLoading::LoadZone(const
|
|||||||
for (auto game = 0u; game < static_cast<unsigned>(GameId::COUNT); game++)
|
for (auto game = 0u; game < static_cast<unsigned>(GameId::COUNT); game++)
|
||||||
{
|
{
|
||||||
const auto* factory = IZoneLoaderFactory::GetZoneLoaderFactoryForGame(static_cast<GameId>(game));
|
const auto* factory = IZoneLoaderFactory::GetZoneLoaderFactoryForGame(static_cast<GameId>(game));
|
||||||
zoneLoader = factory->CreateLoaderForHeader(header, zoneName, std::move(progressCallback));
|
if (factory->InspectZoneHeader(header))
|
||||||
|
{
|
||||||
if (zoneLoader)
|
zoneLoader = factory->CreateLoaderForHeader(header, zoneName, std::move(progressCallback));
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!zoneLoader)
|
if (!zoneLoader)
|
||||||
|
|||||||
Reference in New Issue
Block a user