Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Source/Main.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -443,7 +443,7 @@ int main(int argc, char** argv) {

HandleMainArgs(argc, argv);

g_PresetMan.LoadAllDataModules();
g_PresetMan.LoadAllDataModules(PollSDLEvents);

if (!System::IsInExternalModuleValidationMode()) {
// Load the different input device icons. This can't be done during UInputMan::Create() because the icon presets don't exist so we need to do this after modules are loaded.
Expand Down
2 changes: 1 addition & 1 deletion Source/Managers/ActivityMan.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -352,7 +352,7 @@ bool ActivityMan::LoadAndLaunchGame(const std::string& fileName) {

buffer[info.uncompressed_size] = 0; // null terminate

Reader reader(std::make_unique<std::istringstream>(buffer), filePath + "/Save.ini", true, nullptr, false);
Reader reader(std::make_unique<std::istringstream>(buffer), filePath + "/Save.ini", true, false);

std::unique_ptr<Scene> scene(std::make_unique<Scene>());
std::unique_ptr<GAScripted> activity(std::make_unique<GAScripted>());
Expand Down
299 changes: 253 additions & 46 deletions Source/Managers/PresetMan.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ void PresetMan::Destroy() {
Clear();
}

bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, bool userdata, const ProgressCallback& progressCallback) {
bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, bool userdata) {
if (moduleName.empty()) {
return false;
}
Expand All@@ -87,9 +87,6 @@ bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, boo

// Official modules are stacked in the beginning of the vector.
if (official && !userdata) {
// Halt if an official module is being loaded after any non-official ones!
// RTEAssert(m_pDataModules.size() == m_OfficialModuleCount, "Trying to load an official module after a non-official one has been loaded!");

// Find where the official modules end in the vector.
std::vector<DataModule*>::iterator moduleItr = m_pDataModules.begin();
size_t newModuleID = 0;
Expand All@@ -108,72 +105,124 @@ bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, boo
m_DataModuleIDs.try_emplace(lowercaseName, m_pDataModules.size() - 1);
}

if (newModule->Create(moduleName, progressCallback) < 0) {
if (newModule->Create(moduleName) < 0) {
RTEAbort("Failed to find the " + moduleName + " Data Module!");
return false;
}
newModule = nullptr;
return true;
}

bool PresetMan::LoadAllDataModules() {
auto moduleLoadTimerStart = std::chrono::steady_clock::now();
bool PresetMan::LoadAllDataModules(std::function<void()> PollSDLEventsCallback) {
auto timerTotalFunctionStart = std::chrono::steady_clock::now();
std::chrono::milliseconds moduleLoadElapsedTime = {};

// Destroy any possible loaded modules
Destroy();

FindAndExtractZippedModules();

// Load all the official modules first!
for (const std::string& officialModule: c_OfficialModules) {
if (!LoadDataModule(officialModule, true, false, LoadingScreen::LoadingSplashProgressReport)) {
return false;
}
// No callback passing by a trickle, instead shove needed
// functions into statics of classes that will use them
DataModule::AssertFromWorkerAndShutdownAll = AssertFromModuleLoadingWorkerAndShutdownAll;
Reader::AssertFromWorkerAndShutdownAll = AssertFromModuleLoadingWorkerAndShutdownAll;
if (!g_SettingsMan.GetLoadingScreenProgressReportDisabled()) {
Reader::PushToProgressDisplayQueue = PushToProgressDisplayQueue;
DataModule::PushToProgressDisplayQueue = PushToProgressDisplayQueue;
}

// If a single module is specified, skip loading all other unofficial modules and load specified module only.
if (!m_SingleModuleToLoad.empty() && !IsModuleOfficial(m_SingleModuleToLoad)) {
if (!LoadDataModule(m_SingleModuleToLoad, false, false, LoadingScreen::LoadingSplashProgressReport)) {
g_ConsoleMan.PrintString("ERROR: Failed to load DataModule \"" + m_SingleModuleToLoad + "\"! Only official modules were loaded!");
return false;
}
} else {
std::vector<std::filesystem::directory_entry> modDirectoryFolders;
const std::string modDirectory = System::GetWorkingDirectory() + System::GetModDirectory();
std::copy_if(std::filesystem::directory_iterator(modDirectory), std::filesystem::directory_iterator(), std::back_inserter(modDirectoryFolders),
[](auto dirEntry) { return std::filesystem::is_directory(dirEntry); });
std::sort(modDirectoryFolders.begin(), modDirectoryFolders.end());

for (const std::filesystem::directory_entry& directoryEntry: modDirectoryFolders) {
std::string directoryEntryPath = directoryEntry.path().generic_string();
if (directoryEntryPath.ends_with(".rte")) {
std::string moduleName = directoryEntryPath.substr(directoryEntryPath.find_last_of('/') + 1, std::string::npos);
if (!g_SettingsMan.IsModDisabled(moduleName) && !IsModuleOfficial(moduleName) && !IsModuleUserdata(moduleName)) {
int moduleID = GetModuleID(moduleName);
// NOTE: LoadDataModule can return false (especially since it may try to load already loaded modules, which is okay) and shouldn't cause stop, so we can ignore its return value here.
if (moduleID < 0 || moduleID >= GetOfficialModuleCount()) {
LoadDataModule(moduleName, false, false, LoadingScreen::LoadingSplashProgressReport);
// Module loading Thread
std::atomic<ModuleLoadResult> loadingDone = ModuleLoadResult::StillWorking;
bool toDoProgressPrintOut = !g_SettingsMan.GetLoadingScreenProgressReportDisabled();
std::jthread moduleLoadingThread([&](std::stop_token st) {
ModuleLoadingThreadFunction(st, loadingDone, moduleLoadElapsedTime);
});

// Spinlock watchdog thread
std::atomic<int> mainThreadHeartbeat = 0;
std::atomic<bool> spinlockDetected = false;
std::jthread spinlockWatchdogThread([&](std::stop_token st) {
SpinlockWatchdogThreadFunction(st, mainThreadHeartbeat, spinlockDetected);
});

// Main thread - we drain display queue and poll SDL events
//
// We do this so the window does not freeze, and to handle
// the close button on the window being hit / Alt+F4 pressed
{
while (1) {
mainThreadHeartbeat++;
PollSDLEventsCallback();
if (true) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for the if (true). you can just make a code block, that's still local scope.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A very non-production PR from me, sorry! This and other things i intended not to have left in. Will fix up, thank you for great feedback!

ProgressDisplayEntry entry;
{
std::unique_lock lk(m_ProgressDisplayMutex);

// Sleep until there is work to do or 16 ms pass
m_ProgressDisplayCv.wait_for(lk, std::chrono::milliseconds(16), [&] {
return
!m_ProgressDisplayDeque.empty()
|| loadingDone != ModuleLoadResult::StillWorking
|| m_WorkerFailed
|| spinlockDetected
|| System::IsSetToQuit();
});

if (System::IsSetToQuit()
|| loadingDone == ModuleLoadResult::Failure
|| m_WorkerFailed)
{
moduleLoadingThread.request_stop();
break;
}

if (loadingDone == ModuleLoadResult::Success
&& m_ProgressDisplayDeque.empty())
{
break;
}

if (!m_ProgressDisplayDeque.empty()) {
entry = std::move(m_ProgressDisplayDeque.front());
m_ProgressDisplayDeque.pop_front();
} else if (spinlockDetected) {
spinlockDetected = false;
SpinlockAssert(toDoProgressPrintOut, loadingDone);
RTEAssert(false, to_string(mainThreadHeartbeat))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw we have RTEAbort for this usecase

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I learn more things 🙂
Will use!

}
}
LoadingScreen::LoadingSplashProgressReport(entry.first, entry.second);
}
}
}

// Load userdata modules AFTER all other techs etc are loaded; might be referring to stuff in user mods.
for (const auto& [userdataModuleName, userdataModuleFriendlyName]: c_UserdataModules) {
if (!std::filesystem::exists(System::GetWorkingDirectory() + System::GetUserdataDirectory() + userdataModuleName)) {
bool scanContentsAndIgnoreMissing = userdataModuleName == c_UserScenesModuleName;
DataModule::CreateOnDiskAsUserdata(userdataModuleName, userdataModuleFriendlyName, scanContentsAndIgnoreMissing, scanContentsAndIgnoreMissing);
}
if (!LoadDataModule(userdataModuleName, false, true, LoadingScreen::LoadingSplashProgressReport)) {
return false;
}
}
moduleLoadingThread.join();
spinlockWatchdogThread.request_stop();
m_SpinlockWdCv.notify_all();
spinlockWatchdogThread.join();

if (System::IsSetToQuit()) {
return false;
}

if (loadingDone == ModuleLoadResult::Failure) {
RTEAssert(false, m_WorkerErrorMessage);
}

// Compile the shaders we've deferred
for (auto* shader: m_ShadersToCompile) {
shader->Create();
}

if (g_SettingsMan.IsMeasuringModuleLoadTime()) {
std::chrono::milliseconds moduleLoadElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - moduleLoadTimerStart);
g_ConsoleMan.PrintString("Module load duration is: " + std::to_string(moduleLoadElapsedTime.count()) + "ms");
std::chrono::milliseconds totalFunctionElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - timerTotalFunctionStart);
std::string coutString = "Total loading time was " + std::to_string(totalFunctionElapsedTime.count()) + "ms";
if (!g_SettingsMan.GetLoadingScreenProgressReportDisabled()) {
coutString += " (module load duration: " + std::to_string(moduleLoadElapsedTime.count()) + "ms)";
}
g_ConsoleMan.PrintString(coutString);
}

return true;
}

Expand DownExpand Up@@ -350,6 +399,9 @@ const Entity* PresetMan::GetEntityPreset(Reader& reader) {
// Load class name and then preset instance
reader >> ClassName;
pClass = Entity::ClassInfo::GetClass(ClassName);
if (ClassName == "Shader") {
int a = 5;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???? (though if you really need this empty check to happen use (void); instead)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh this was to set a breakpoint on it 😅

}

if (pClass && pClass->IsConcrete()) {
// Instantiate
Expand DownExpand Up@@ -919,3 +971,158 @@ void PresetMan::FindAndExtractZippedModules() const {
}
}
}

void PresetMan::DeferShaderCompilationToBeDoneOnMainThread(Shader* shader) {
m_ShadersToCompile.push_back(shader);
}

void PresetMan::PushToProgressDisplayQueue(const std::string& string, bool newItem) {
{
std::lock_guard lg(g_PresetMan.m_ProgressDisplayMutex);
g_PresetMan.m_ProgressDisplayDeque.push_back({string, newItem});
}
g_PresetMan.m_ProgressDisplayCv.notify_one();
}

void PresetMan::AssertFromModuleLoadingWorkerAndShutdownAll(const std::string& assertString) {
g_PresetMan.m_WorkerFailed = true;
g_PresetMan.m_ProgressDisplayCv.notify_all();
}

void PresetMan::SpinlockAssert(bool toDoProgressPrintOut, ModuleLoadResult loadingDone) {

auto loadingDoneValueString = [](auto& loadingDone) -> const std::string {
switch (loadingDone) {
case ModuleLoadResult::Failure:
return "Failure";
case ModuleLoadResult::Success:
return "Success";
case ModuleLoadResult::StillWorking:
return "StillWorking";
default:
return "Unknown";
}
};
std::string assertString =
"Main thread spinlock during module loading!\n"
+ std::string("toDoProgressPrintOut = ") + std::to_string(toDoProgressPrintOut)
+ std::string("\nloadingDone = ") + loadingDoneValueString(loadingDone)
+ std::string("\nm_WorkerFailed = ") + std::to_string(m_WorkerFailed)
+ std::string("\nm_ProgressDisplayDeque.size() = ") + std::to_string(m_ProgressDisplayDeque.size());
RTEAssert(false, assertString);
}

void PresetMan::ModuleLoadingThreadFunction(std::stop_token st, std::atomic<ModuleLoadResult>& loadingDone, std::chrono::milliseconds& moduleLoadElapsedTime) {
try {
auto timerModuleLoadingThreadStart = std::chrono::steady_clock::now();
// auto ProgressCallback = g_SettingsMan.GetLoadingScreenProgressReportDisabled()
// ? nullptr
// : PresetMan::PushToProgressDisplayQueue;
// Load all the official modules first!
for (const std::string& officialModule: c_OfficialModules) {
if (st.stop_requested()) {
return;
}
if (!LoadDataModule(officialModule, true, false)) {
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
}

// If a single module is specified, skip loading all other unofficial modules and load specified module only.
if (!m_SingleModuleToLoad.empty() && !IsModuleOfficial(m_SingleModuleToLoad)) {
if (!LoadDataModule(m_SingleModuleToLoad, false, false)) {
g_ConsoleMan.PrintString("ERROR: Failed to load DataModule \"" + m_SingleModuleToLoad + "\"! Only official modules were loaded!");
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
} else {
std::vector<std::filesystem::directory_entry> modDirectoryFolders;
const std::string modDirectory = System::GetWorkingDirectory() + System::GetModDirectory();
std::copy_if(std::filesystem::directory_iterator(modDirectory), std::filesystem::directory_iterator(), std::back_inserter(modDirectoryFolders),
[](auto dirEntry) { return std::filesystem::is_directory(dirEntry); });
std::sort(modDirectoryFolders.begin(), modDirectoryFolders.end());

for (const std::filesystem::directory_entry& directoryEntry: modDirectoryFolders) {
if (st.stop_requested()) {
return;
}
std::string directoryEntryPath = directoryEntry.path().generic_string();
if (directoryEntryPath.ends_with(".rte")) {
std::string moduleName = directoryEntryPath.substr(directoryEntryPath.find_last_of('/') + 1, std::string::npos);
if (!g_SettingsMan.IsModDisabled(moduleName) && !IsModuleOfficial(moduleName) && !IsModuleUserdata(moduleName)) {
int moduleID = GetModuleID(moduleName);
// NOTE: LoadDataModule can return false (especially since it may try to load already loaded modules, which is okay) and shouldn't cause stop, so we can ignore its return value here.
if (moduleID < 0 || moduleID >= GetOfficialModuleCount()) {
LoadDataModule(moduleName, false, false);
}
}
}
}

// Load userdata modules AFTER all other techs etc are loaded; might be referring to stuff in user mods.
for (const auto& [userdataModuleName, userdataModuleFriendlyName]: c_UserdataModules) {
if (st.stop_requested()) {
return;
}
if (!std::filesystem::exists(System::GetWorkingDirectory() + System::GetUserdataDirectory() + userdataModuleName)) {
bool scanContentsAndIgnoreMissing = userdataModuleName == c_UserScenesModuleName;
DataModule::CreateOnDiskAsUserdata(userdataModuleName, userdataModuleFriendlyName, scanContentsAndIgnoreMissing, scanContentsAndIgnoreMissing);
}
if (!LoadDataModule(userdataModuleName, false, true)) {
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
}
}

moduleLoadElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - timerModuleLoadingThreadStart);

loadingDone = ModuleLoadResult::Success;
m_ProgressDisplayCv.notify_all();
return;
} catch (const std::exception& e) {
AssertFromModuleLoadingWorkerAndShutdownAll(std::string("Module loader exception!\n") + e.what());
m_ToStopSpinlockWatchdog = true;
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
} catch (...) {
AssertFromModuleLoadingWorkerAndShutdownAll("Module loader unknown exception!");
m_ToStopSpinlockWatchdog = true;
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
}

// We only reach here if we've caught an exception
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
}

void PresetMan::SpinlockWatchdogThreadFunction(std::stop_token st, std::atomic<int>& mainThreadHeartbeat, std::atomic<bool>& spinlockDetected) {
int previousHeartbeat = 0;
while (!st.stop_requested()) {
if (m_ToStopSpinlockWatchdog) {
return;
}

std::unique_lock lk(m_SpinlockWdMutex);
m_SpinlockWdCv.wait_for(lk, std::chrono::milliseconds(1000), [&] {
return st.stop_requested();
});
if (st.stop_requested()) {
return;
}

int newHeartbeat = mainThreadHeartbeat;
if (newHeartbeat == previousHeartbeat) {
// Spinlock, report!
spinlockDetected = true;
m_ProgressDisplayCv.notify_all();
return;
}
previousHeartbeat = newHeartbeat;
}
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Source/Main.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -443,7 +443,7 @@ int main(int argc, char** argv) {

HandleMainArgs(argc, argv);

g_PresetMan.LoadAllDataModules();
g_PresetMan.LoadAllDataModules(PollSDLEvents);

if (!System::IsInExternalModuleValidationMode()) {
// Load the different input device icons. This can't be done during UInputMan::Create() because the icon presets don't exist so we need to do this after modules are loaded.
Expand Down
2 changes: 1 addition & 1 deletion Source/Managers/ActivityMan.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -352,7 +352,7 @@ bool ActivityMan::LoadAndLaunchGame(const std::string& fileName) {

buffer[info.uncompressed_size] = 0; // null terminate

Reader reader(std::make_unique<std::istringstream>(buffer), filePath + "/Save.ini", true, nullptr, false);
Reader reader(std::make_unique<std::istringstream>(buffer), filePath + "/Save.ini", true, false);

std::unique_ptr<Scene> scene(std::make_unique<Scene>());
std::unique_ptr<GAScripted> activity(std::make_unique<GAScripted>());
Expand Down
299 changes: 253 additions & 46 deletions Source/Managers/PresetMan.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ void PresetMan::Destroy() {
Clear();
}

bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, bool userdata, const ProgressCallback& progressCallback) {
bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, bool userdata) {
if (moduleName.empty()) {
return false;
}
Expand All@@ -87,9 +87,6 @@ bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, boo

// Official modules are stacked in the beginning of the vector.
if (official && !userdata) {
// Halt if an official module is being loaded after any non-official ones!
// RTEAssert(m_pDataModules.size() == m_OfficialModuleCount, "Trying to load an official module after a non-official one has been loaded!");

// Find where the official modules end in the vector.
std::vector<DataModule*>::iterator moduleItr = m_pDataModules.begin();
size_t newModuleID = 0;
Expand All@@ -108,72 +105,124 @@ bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, boo
m_DataModuleIDs.try_emplace(lowercaseName, m_pDataModules.size() - 1);
}

if (newModule->Create(moduleName, progressCallback) < 0) {
if (newModule->Create(moduleName) < 0) {
RTEAbort("Failed to find the " + moduleName + " Data Module!");
return false;
}
newModule = nullptr;
return true;
}

bool PresetMan::LoadAllDataModules() {
auto moduleLoadTimerStart = std::chrono::steady_clock::now();
bool PresetMan::LoadAllDataModules(std::function<void()> PollSDLEventsCallback) {
auto timerTotalFunctionStart = std::chrono::steady_clock::now();
std::chrono::milliseconds moduleLoadElapsedTime = {};

// Destroy any possible loaded modules
Destroy();

FindAndExtractZippedModules();

// Load all the official modules first!
for (const std::string& officialModule: c_OfficialModules) {
if (!LoadDataModule(officialModule, true, false, LoadingScreen::LoadingSplashProgressReport)) {
return false;
}
// No callback passing by a trickle, instead shove needed
// functions into statics of classes that will use them
DataModule::AssertFromWorkerAndShutdownAll = AssertFromModuleLoadingWorkerAndShutdownAll;
Reader::AssertFromWorkerAndShutdownAll = AssertFromModuleLoadingWorkerAndShutdownAll;
if (!g_SettingsMan.GetLoadingScreenProgressReportDisabled()) {
Reader::PushToProgressDisplayQueue = PushToProgressDisplayQueue;
DataModule::PushToProgressDisplayQueue = PushToProgressDisplayQueue;
}

// If a single module is specified, skip loading all other unofficial modules and load specified module only.
if (!m_SingleModuleToLoad.empty() && !IsModuleOfficial(m_SingleModuleToLoad)) {
if (!LoadDataModule(m_SingleModuleToLoad, false, false, LoadingScreen::LoadingSplashProgressReport)) {
g_ConsoleMan.PrintString("ERROR: Failed to load DataModule \"" + m_SingleModuleToLoad + "\"! Only official modules were loaded!");
return false;
}
} else {
std::vector<std::filesystem::directory_entry> modDirectoryFolders;
const std::string modDirectory = System::GetWorkingDirectory() + System::GetModDirectory();
std::copy_if(std::filesystem::directory_iterator(modDirectory), std::filesystem::directory_iterator(), std::back_inserter(modDirectoryFolders),
[](auto dirEntry) { return std::filesystem::is_directory(dirEntry); });
std::sort(modDirectoryFolders.begin(), modDirectoryFolders.end());

for (const std::filesystem::directory_entry& directoryEntry: modDirectoryFolders) {
std::string directoryEntryPath = directoryEntry.path().generic_string();
if (directoryEntryPath.ends_with(".rte")) {
std::string moduleName = directoryEntryPath.substr(directoryEntryPath.find_last_of('/') + 1, std::string::npos);
if (!g_SettingsMan.IsModDisabled(moduleName) && !IsModuleOfficial(moduleName) && !IsModuleUserdata(moduleName)) {
int moduleID = GetModuleID(moduleName);
// NOTE: LoadDataModule can return false (especially since it may try to load already loaded modules, which is okay) and shouldn't cause stop, so we can ignore its return value here.
if (moduleID < 0 || moduleID >= GetOfficialModuleCount()) {
LoadDataModule(moduleName, false, false, LoadingScreen::LoadingSplashProgressReport);
// Module loading Thread
std::atomic<ModuleLoadResult> loadingDone = ModuleLoadResult::StillWorking;
bool toDoProgressPrintOut = !g_SettingsMan.GetLoadingScreenProgressReportDisabled();
std::jthread moduleLoadingThread([&](std::stop_token st) {
ModuleLoadingThreadFunction(st, loadingDone, moduleLoadElapsedTime);
});

// Spinlock watchdog thread
std::atomic<int> mainThreadHeartbeat = 0;
std::atomic<bool> spinlockDetected = false;
std::jthread spinlockWatchdogThread([&](std::stop_token st) {
SpinlockWatchdogThreadFunction(st, mainThreadHeartbeat, spinlockDetected);
});

// Main thread - we drain display queue and poll SDL events
//
// We do this so the window does not freeze, and to handle
// the close button on the window being hit / Alt+F4 pressed
{
while (1) {
mainThreadHeartbeat++;
PollSDLEventsCallback();
if (true) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for the if (true). you can just make a code block, that's still local scope.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A very non-production PR from me, sorry! This and other things i intended not to have left in. Will fix up, thank you for great feedback!

ProgressDisplayEntry entry;
{
std::unique_lock lk(m_ProgressDisplayMutex);

// Sleep until there is work to do or 16 ms pass
m_ProgressDisplayCv.wait_for(lk, std::chrono::milliseconds(16), [&] {
return
!m_ProgressDisplayDeque.empty()
|| loadingDone != ModuleLoadResult::StillWorking
|| m_WorkerFailed
|| spinlockDetected
|| System::IsSetToQuit();
});

if (System::IsSetToQuit()
|| loadingDone == ModuleLoadResult::Failure
|| m_WorkerFailed)
{
moduleLoadingThread.request_stop();
break;
}

if (loadingDone == ModuleLoadResult::Success
&& m_ProgressDisplayDeque.empty())
{
break;
}

if (!m_ProgressDisplayDeque.empty()) {
entry = std::move(m_ProgressDisplayDeque.front());
m_ProgressDisplayDeque.pop_front();
} else if (spinlockDetected) {
spinlockDetected = false;
SpinlockAssert(toDoProgressPrintOut, loadingDone);
RTEAssert(false, to_string(mainThreadHeartbeat))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw we have RTEAbort for this usecase

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I learn more things 🙂
Will use!

}
}
LoadingScreen::LoadingSplashProgressReport(entry.first, entry.second);
}
}
}

// Load userdata modules AFTER all other techs etc are loaded; might be referring to stuff in user mods.
for (const auto& [userdataModuleName, userdataModuleFriendlyName]: c_UserdataModules) {
if (!std::filesystem::exists(System::GetWorkingDirectory() + System::GetUserdataDirectory() + userdataModuleName)) {
bool scanContentsAndIgnoreMissing = userdataModuleName == c_UserScenesModuleName;
DataModule::CreateOnDiskAsUserdata(userdataModuleName, userdataModuleFriendlyName, scanContentsAndIgnoreMissing, scanContentsAndIgnoreMissing);
}
if (!LoadDataModule(userdataModuleName, false, true, LoadingScreen::LoadingSplashProgressReport)) {
return false;
}
}
moduleLoadingThread.join();
spinlockWatchdogThread.request_stop();
m_SpinlockWdCv.notify_all();
spinlockWatchdogThread.join();

if (System::IsSetToQuit()) {
return false;
}

if (loadingDone == ModuleLoadResult::Failure) {
RTEAssert(false, m_WorkerErrorMessage);
}

// Compile the shaders we've deferred
for (auto* shader: m_ShadersToCompile) {
shader->Create();
}

if (g_SettingsMan.IsMeasuringModuleLoadTime()) {
std::chrono::milliseconds moduleLoadElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - moduleLoadTimerStart);
g_ConsoleMan.PrintString("Module load duration is: " + std::to_string(moduleLoadElapsedTime.count()) + "ms");
std::chrono::milliseconds totalFunctionElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - timerTotalFunctionStart);
std::string coutString = "Total loading time was " + std::to_string(totalFunctionElapsedTime.count()) + "ms";
if (!g_SettingsMan.GetLoadingScreenProgressReportDisabled()) {
coutString += " (module load duration: " + std::to_string(moduleLoadElapsedTime.count()) + "ms)";
}
g_ConsoleMan.PrintString(coutString);
}

return true;
}

Expand DownExpand Up@@ -350,6 +399,9 @@ const Entity* PresetMan::GetEntityPreset(Reader& reader) {
// Load class name and then preset instance
reader >> ClassName;
pClass = Entity::ClassInfo::GetClass(ClassName);
if (ClassName == "Shader") {
int a = 5;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???? (though if you really need this empty check to happen use (void); instead)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh this was to set a breakpoint on it 😅

}

if (pClass && pClass->IsConcrete()) {
// Instantiate
Expand DownExpand Up@@ -919,3 +971,158 @@ void PresetMan::FindAndExtractZippedModules() const {
}
}
}

void PresetMan::DeferShaderCompilationToBeDoneOnMainThread(Shader* shader) {
m_ShadersToCompile.push_back(shader);
}

void PresetMan::PushToProgressDisplayQueue(const std::string& string, bool newItem) {
{
std::lock_guard lg(g_PresetMan.m_ProgressDisplayMutex);
g_PresetMan.m_ProgressDisplayDeque.push_back({string, newItem});
}
g_PresetMan.m_ProgressDisplayCv.notify_one();
}

void PresetMan::AssertFromModuleLoadingWorkerAndShutdownAll(const std::string& assertString) {
g_PresetMan.m_WorkerFailed = true;
g_PresetMan.m_ProgressDisplayCv.notify_all();
}

void PresetMan::SpinlockAssert(bool toDoProgressPrintOut, ModuleLoadResult loadingDone) {

auto loadingDoneValueString = [](auto& loadingDone) -> const std::string {
switch (loadingDone) {
case ModuleLoadResult::Failure:
return "Failure";
case ModuleLoadResult::Success:
return "Success";
case ModuleLoadResult::StillWorking:
return "StillWorking";
default:
return "Unknown";
}
};
std::string assertString =
"Main thread spinlock during module loading!\n"
+ std::string("toDoProgressPrintOut = ") + std::to_string(toDoProgressPrintOut)
+ std::string("\nloadingDone = ") + loadingDoneValueString(loadingDone)
+ std::string("\nm_WorkerFailed = ") + std::to_string(m_WorkerFailed)
+ std::string("\nm_ProgressDisplayDeque.size() = ") + std::to_string(m_ProgressDisplayDeque.size());
RTEAssert(false, assertString);
}

void PresetMan::ModuleLoadingThreadFunction(std::stop_token st, std::atomic<ModuleLoadResult>& loadingDone, std::chrono::milliseconds& moduleLoadElapsedTime) {
try {
auto timerModuleLoadingThreadStart = std::chrono::steady_clock::now();
// auto ProgressCallback = g_SettingsMan.GetLoadingScreenProgressReportDisabled()
// ? nullptr
// : PresetMan::PushToProgressDisplayQueue;
// Load all the official modules first!
for (const std::string& officialModule: c_OfficialModules) {
if (st.stop_requested()) {
return;
}
if (!LoadDataModule(officialModule, true, false)) {
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
}

// If a single module is specified, skip loading all other unofficial modules and load specified module only.
if (!m_SingleModuleToLoad.empty() && !IsModuleOfficial(m_SingleModuleToLoad)) {
if (!LoadDataModule(m_SingleModuleToLoad, false, false)) {
g_ConsoleMan.PrintString("ERROR: Failed to load DataModule \"" + m_SingleModuleToLoad + "\"! Only official modules were loaded!");
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
} else {
std::vector<std::filesystem::directory_entry> modDirectoryFolders;
const std::string modDirectory = System::GetWorkingDirectory() + System::GetModDirectory();
std::copy_if(std::filesystem::directory_iterator(modDirectory), std::filesystem::directory_iterator(), std::back_inserter(modDirectoryFolders),
[](auto dirEntry) { return std::filesystem::is_directory(dirEntry); });
std::sort(modDirectoryFolders.begin(), modDirectoryFolders.end());

for (const std::filesystem::directory_entry& directoryEntry: modDirectoryFolders) {
if (st.stop_requested()) {
return;
}
std::string directoryEntryPath = directoryEntry.path().generic_string();
if (directoryEntryPath.ends_with(".rte")) {
std::string moduleName = directoryEntryPath.substr(directoryEntryPath.find_last_of('/') + 1, std::string::npos);
if (!g_SettingsMan.IsModDisabled(moduleName) && !IsModuleOfficial(moduleName) && !IsModuleUserdata(moduleName)) {
int moduleID = GetModuleID(moduleName);
// NOTE: LoadDataModule can return false (especially since it may try to load already loaded modules, which is okay) and shouldn't cause stop, so we can ignore its return value here.
if (moduleID < 0 || moduleID >= GetOfficialModuleCount()) {
LoadDataModule(moduleName, false, false);
}
}
}
}

// Load userdata modules AFTER all other techs etc are loaded; might be referring to stuff in user mods.
for (const auto& [userdataModuleName, userdataModuleFriendlyName]: c_UserdataModules) {
if (st.stop_requested()) {
return;
}
if (!std::filesystem::exists(System::GetWorkingDirectory() + System::GetUserdataDirectory() + userdataModuleName)) {
bool scanContentsAndIgnoreMissing = userdataModuleName == c_UserScenesModuleName;
DataModule::CreateOnDiskAsUserdata(userdataModuleName, userdataModuleFriendlyName, scanContentsAndIgnoreMissing, scanContentsAndIgnoreMissing);
}
if (!LoadDataModule(userdataModuleName, false, true)) {
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
}
}

moduleLoadElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - timerModuleLoadingThreadStart);

loadingDone = ModuleLoadResult::Success;
m_ProgressDisplayCv.notify_all();
return;
} catch (const std::exception& e) {
AssertFromModuleLoadingWorkerAndShutdownAll(std::string("Module loader exception!\n") + e.what());
m_ToStopSpinlockWatchdog = true;
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
} catch (...) {
AssertFromModuleLoadingWorkerAndShutdownAll("Module loader unknown exception!");
m_ToStopSpinlockWatchdog = true;
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
}

// We only reach here if we've caught an exception
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
}

void PresetMan::SpinlockWatchdogThreadFunction(std::stop_token st, std::atomic<int>& mainThreadHeartbeat, std::atomic<bool>& spinlockDetected) {
int previousHeartbeat = 0;
while (!st.stop_requested()) {
if (m_ToStopSpinlockWatchdog) {
return;
}

std::unique_lock lk(m_SpinlockWdMutex);
m_SpinlockWdCv.wait_for(lk, std::chrono::milliseconds(1000), [&] {
return st.stop_requested();
});
if (st.stop_requested()) {
return;
}

int newHeartbeat = mainThreadHeartbeat;
if (newHeartbeat == previousHeartbeat) {
// Spinlock, report!
spinlockDetected = true;
m_ProgressDisplayCv.notify_all();
return;
}
previousHeartbeat = newHeartbeat;
}
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Source/Main.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -443,7 +443,7 @@ int main(int argc, char** argv) {

HandleMainArgs(argc, argv);

g_PresetMan.LoadAllDataModules();
g_PresetMan.LoadAllDataModules(PollSDLEvents);

if (!System::IsInExternalModuleValidationMode()) {
// Load the different input device icons. This can't be done during UInputMan::Create() because the icon presets don't exist so we need to do this after modules are loaded.
Expand Down
2 changes: 1 addition & 1 deletion Source/Managers/ActivityMan.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -352,7 +352,7 @@ bool ActivityMan::LoadAndLaunchGame(const std::string& fileName) {

buffer[info.uncompressed_size] = 0; // null terminate

Reader reader(std::make_unique<std::istringstream>(buffer), filePath + "/Save.ini", true, nullptr, false);
Reader reader(std::make_unique<std::istringstream>(buffer), filePath + "/Save.ini", true, false);

std::unique_ptr<Scene> scene(std::make_unique<Scene>());
std::unique_ptr<GAScripted> activity(std::make_unique<GAScripted>());
Expand Down
299 changes: 253 additions & 46 deletions Source/Managers/PresetMan.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ void PresetMan::Destroy() {
Clear();
}

bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, bool userdata, const ProgressCallback& progressCallback) {
bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, bool userdata) {
if (moduleName.empty()) {
return false;
}
Expand All@@ -87,9 +87,6 @@ bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, boo

// Official modules are stacked in the beginning of the vector.
if (official && !userdata) {
// Halt if an official module is being loaded after any non-official ones!
// RTEAssert(m_pDataModules.size() == m_OfficialModuleCount, "Trying to load an official module after a non-official one has been loaded!");

// Find where the official modules end in the vector.
std::vector<DataModule*>::iterator moduleItr = m_pDataModules.begin();
size_t newModuleID = 0;
Expand All@@ -108,72 +105,124 @@ bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, boo
m_DataModuleIDs.try_emplace(lowercaseName, m_pDataModules.size() - 1);
}

if (newModule->Create(moduleName, progressCallback) < 0) {
if (newModule->Create(moduleName) < 0) {
RTEAbort("Failed to find the " + moduleName + " Data Module!");
return false;
}
newModule = nullptr;
return true;
}

bool PresetMan::LoadAllDataModules() {
auto moduleLoadTimerStart = std::chrono::steady_clock::now();
bool PresetMan::LoadAllDataModules(std::function<void()> PollSDLEventsCallback) {
auto timerTotalFunctionStart = std::chrono::steady_clock::now();
std::chrono::milliseconds moduleLoadElapsedTime = {};

// Destroy any possible loaded modules
Destroy();

FindAndExtractZippedModules();

// Load all the official modules first!
for (const std::string& officialModule: c_OfficialModules) {
if (!LoadDataModule(officialModule, true, false, LoadingScreen::LoadingSplashProgressReport)) {
return false;
}
// No callback passing by a trickle, instead shove needed
// functions into statics of classes that will use them
DataModule::AssertFromWorkerAndShutdownAll = AssertFromModuleLoadingWorkerAndShutdownAll;
Reader::AssertFromWorkerAndShutdownAll = AssertFromModuleLoadingWorkerAndShutdownAll;
if (!g_SettingsMan.GetLoadingScreenProgressReportDisabled()) {
Reader::PushToProgressDisplayQueue = PushToProgressDisplayQueue;
DataModule::PushToProgressDisplayQueue = PushToProgressDisplayQueue;
}

// If a single module is specified, skip loading all other unofficial modules and load specified module only.
if (!m_SingleModuleToLoad.empty() && !IsModuleOfficial(m_SingleModuleToLoad)) {
if (!LoadDataModule(m_SingleModuleToLoad, false, false, LoadingScreen::LoadingSplashProgressReport)) {
g_ConsoleMan.PrintString("ERROR: Failed to load DataModule \"" + m_SingleModuleToLoad + "\"! Only official modules were loaded!");
return false;
}
} else {
std::vector<std::filesystem::directory_entry> modDirectoryFolders;
const std::string modDirectory = System::GetWorkingDirectory() + System::GetModDirectory();
std::copy_if(std::filesystem::directory_iterator(modDirectory), std::filesystem::directory_iterator(), std::back_inserter(modDirectoryFolders),
[](auto dirEntry) { return std::filesystem::is_directory(dirEntry); });
std::sort(modDirectoryFolders.begin(), modDirectoryFolders.end());

for (const std::filesystem::directory_entry& directoryEntry: modDirectoryFolders) {
std::string directoryEntryPath = directoryEntry.path().generic_string();
if (directoryEntryPath.ends_with(".rte")) {
std::string moduleName = directoryEntryPath.substr(directoryEntryPath.find_last_of('/') + 1, std::string::npos);
if (!g_SettingsMan.IsModDisabled(moduleName) && !IsModuleOfficial(moduleName) && !IsModuleUserdata(moduleName)) {
int moduleID = GetModuleID(moduleName);
// NOTE: LoadDataModule can return false (especially since it may try to load already loaded modules, which is okay) and shouldn't cause stop, so we can ignore its return value here.
if (moduleID < 0 || moduleID >= GetOfficialModuleCount()) {
LoadDataModule(moduleName, false, false, LoadingScreen::LoadingSplashProgressReport);
// Module loading Thread
std::atomic<ModuleLoadResult> loadingDone = ModuleLoadResult::StillWorking;
bool toDoProgressPrintOut = !g_SettingsMan.GetLoadingScreenProgressReportDisabled();
std::jthread moduleLoadingThread([&](std::stop_token st) {
ModuleLoadingThreadFunction(st, loadingDone, moduleLoadElapsedTime);
});

// Spinlock watchdog thread
std::atomic<int> mainThreadHeartbeat = 0;
std::atomic<bool> spinlockDetected = false;
std::jthread spinlockWatchdogThread([&](std::stop_token st) {
SpinlockWatchdogThreadFunction(st, mainThreadHeartbeat, spinlockDetected);
});

// Main thread - we drain display queue and poll SDL events
//
// We do this so the window does not freeze, and to handle
// the close button on the window being hit / Alt+F4 pressed
{
while (1) {
mainThreadHeartbeat++;
PollSDLEventsCallback();
if (true) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for the if (true). you can just make a code block, that's still local scope.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A very non-production PR from me, sorry! This and other things i intended not to have left in. Will fix up, thank you for great feedback!

ProgressDisplayEntry entry;
{
std::unique_lock lk(m_ProgressDisplayMutex);

// Sleep until there is work to do or 16 ms pass
m_ProgressDisplayCv.wait_for(lk, std::chrono::milliseconds(16), [&] {
return
!m_ProgressDisplayDeque.empty()
|| loadingDone != ModuleLoadResult::StillWorking
|| m_WorkerFailed
|| spinlockDetected
|| System::IsSetToQuit();
});

if (System::IsSetToQuit()
|| loadingDone == ModuleLoadResult::Failure
|| m_WorkerFailed)
{
moduleLoadingThread.request_stop();
break;
}

if (loadingDone == ModuleLoadResult::Success
&& m_ProgressDisplayDeque.empty())
{
break;
}

if (!m_ProgressDisplayDeque.empty()) {
entry = std::move(m_ProgressDisplayDeque.front());
m_ProgressDisplayDeque.pop_front();
} else if (spinlockDetected) {
spinlockDetected = false;
SpinlockAssert(toDoProgressPrintOut, loadingDone);
RTEAssert(false, to_string(mainThreadHeartbeat))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw we have RTEAbort for this usecase

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I learn more things 🙂
Will use!

}
}
LoadingScreen::LoadingSplashProgressReport(entry.first, entry.second);
}
}
}

// Load userdata modules AFTER all other techs etc are loaded; might be referring to stuff in user mods.
for (const auto& [userdataModuleName, userdataModuleFriendlyName]: c_UserdataModules) {
if (!std::filesystem::exists(System::GetWorkingDirectory() + System::GetUserdataDirectory() + userdataModuleName)) {
bool scanContentsAndIgnoreMissing = userdataModuleName == c_UserScenesModuleName;
DataModule::CreateOnDiskAsUserdata(userdataModuleName, userdataModuleFriendlyName, scanContentsAndIgnoreMissing, scanContentsAndIgnoreMissing);
}
if (!LoadDataModule(userdataModuleName, false, true, LoadingScreen::LoadingSplashProgressReport)) {
return false;
}
}
moduleLoadingThread.join();
spinlockWatchdogThread.request_stop();
m_SpinlockWdCv.notify_all();
spinlockWatchdogThread.join();

if (System::IsSetToQuit()) {
return false;
}

if (loadingDone == ModuleLoadResult::Failure) {
RTEAssert(false, m_WorkerErrorMessage);
}

// Compile the shaders we've deferred
for (auto* shader: m_ShadersToCompile) {
shader->Create();
}

if (g_SettingsMan.IsMeasuringModuleLoadTime()) {
std::chrono::milliseconds moduleLoadElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - moduleLoadTimerStart);
g_ConsoleMan.PrintString("Module load duration is: " + std::to_string(moduleLoadElapsedTime.count()) + "ms");
std::chrono::milliseconds totalFunctionElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - timerTotalFunctionStart);
std::string coutString = "Total loading time was " + std::to_string(totalFunctionElapsedTime.count()) + "ms";
if (!g_SettingsMan.GetLoadingScreenProgressReportDisabled()) {
coutString += " (module load duration: " + std::to_string(moduleLoadElapsedTime.count()) + "ms)";
}
g_ConsoleMan.PrintString(coutString);
}

return true;
}

Expand DownExpand Up@@ -350,6 +399,9 @@ const Entity* PresetMan::GetEntityPreset(Reader& reader) {
// Load class name and then preset instance
reader >> ClassName;
pClass = Entity::ClassInfo::GetClass(ClassName);
if (ClassName == "Shader") {
int a = 5;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???? (though if you really need this empty check to happen use (void); instead)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh this was to set a breakpoint on it 😅

}

if (pClass && pClass->IsConcrete()) {
// Instantiate
Expand DownExpand Up@@ -919,3 +971,158 @@ void PresetMan::FindAndExtractZippedModules() const {
}
}
}

void PresetMan::DeferShaderCompilationToBeDoneOnMainThread(Shader* shader) {
m_ShadersToCompile.push_back(shader);
}

void PresetMan::PushToProgressDisplayQueue(const std::string& string, bool newItem) {
{
std::lock_guard lg(g_PresetMan.m_ProgressDisplayMutex);
g_PresetMan.m_ProgressDisplayDeque.push_back({string, newItem});
}
g_PresetMan.m_ProgressDisplayCv.notify_one();
}

void PresetMan::AssertFromModuleLoadingWorkerAndShutdownAll(const std::string& assertString) {
g_PresetMan.m_WorkerFailed = true;
g_PresetMan.m_ProgressDisplayCv.notify_all();
}

void PresetMan::SpinlockAssert(bool toDoProgressPrintOut, ModuleLoadResult loadingDone) {

auto loadingDoneValueString = [](auto& loadingDone) -> const std::string {
switch (loadingDone) {
case ModuleLoadResult::Failure:
return "Failure";
case ModuleLoadResult::Success:
return "Success";
case ModuleLoadResult::StillWorking:
return "StillWorking";
default:
return "Unknown";
}
};
std::string assertString =
"Main thread spinlock during module loading!\n"
+ std::string("toDoProgressPrintOut = ") + std::to_string(toDoProgressPrintOut)
+ std::string("\nloadingDone = ") + loadingDoneValueString(loadingDone)
+ std::string("\nm_WorkerFailed = ") + std::to_string(m_WorkerFailed)
+ std::string("\nm_ProgressDisplayDeque.size() = ") + std::to_string(m_ProgressDisplayDeque.size());
RTEAssert(false, assertString);
}

void PresetMan::ModuleLoadingThreadFunction(std::stop_token st, std::atomic<ModuleLoadResult>& loadingDone, std::chrono::milliseconds& moduleLoadElapsedTime) {
try {
auto timerModuleLoadingThreadStart = std::chrono::steady_clock::now();
// auto ProgressCallback = g_SettingsMan.GetLoadingScreenProgressReportDisabled()
// ? nullptr
// : PresetMan::PushToProgressDisplayQueue;
// Load all the official modules first!
for (const std::string& officialModule: c_OfficialModules) {
if (st.stop_requested()) {
return;
}
if (!LoadDataModule(officialModule, true, false)) {
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
}

// If a single module is specified, skip loading all other unofficial modules and load specified module only.
if (!m_SingleModuleToLoad.empty() && !IsModuleOfficial(m_SingleModuleToLoad)) {
if (!LoadDataModule(m_SingleModuleToLoad, false, false)) {
g_ConsoleMan.PrintString("ERROR: Failed to load DataModule \"" + m_SingleModuleToLoad + "\"! Only official modules were loaded!");
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
} else {
std::vector<std::filesystem::directory_entry> modDirectoryFolders;
const std::string modDirectory = System::GetWorkingDirectory() + System::GetModDirectory();
std::copy_if(std::filesystem::directory_iterator(modDirectory), std::filesystem::directory_iterator(), std::back_inserter(modDirectoryFolders),
[](auto dirEntry) { return std::filesystem::is_directory(dirEntry); });
std::sort(modDirectoryFolders.begin(), modDirectoryFolders.end());

for (const std::filesystem::directory_entry& directoryEntry: modDirectoryFolders) {
if (st.stop_requested()) {
return;
}
std::string directoryEntryPath = directoryEntry.path().generic_string();
if (directoryEntryPath.ends_with(".rte")) {
std::string moduleName = directoryEntryPath.substr(directoryEntryPath.find_last_of('/') + 1, std::string::npos);
if (!g_SettingsMan.IsModDisabled(moduleName) && !IsModuleOfficial(moduleName) && !IsModuleUserdata(moduleName)) {
int moduleID = GetModuleID(moduleName);
// NOTE: LoadDataModule can return false (especially since it may try to load already loaded modules, which is okay) and shouldn't cause stop, so we can ignore its return value here.
if (moduleID < 0 || moduleID >= GetOfficialModuleCount()) {
LoadDataModule(moduleName, false, false);
}
}
}
}

// Load userdata modules AFTER all other techs etc are loaded; might be referring to stuff in user mods.
for (const auto& [userdataModuleName, userdataModuleFriendlyName]: c_UserdataModules) {
if (st.stop_requested()) {
return;
}
if (!std::filesystem::exists(System::GetWorkingDirectory() + System::GetUserdataDirectory() + userdataModuleName)) {
bool scanContentsAndIgnoreMissing = userdataModuleName == c_UserScenesModuleName;
DataModule::CreateOnDiskAsUserdata(userdataModuleName, userdataModuleFriendlyName, scanContentsAndIgnoreMissing, scanContentsAndIgnoreMissing);
}
if (!LoadDataModule(userdataModuleName, false, true)) {
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
}
}

moduleLoadElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - timerModuleLoadingThreadStart);

loadingDone = ModuleLoadResult::Success;
m_ProgressDisplayCv.notify_all();
return;
} catch (const std::exception& e) {
AssertFromModuleLoadingWorkerAndShutdownAll(std::string("Module loader exception!\n") + e.what());
m_ToStopSpinlockWatchdog = true;
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
} catch (...) {
AssertFromModuleLoadingWorkerAndShutdownAll("Module loader unknown exception!");
m_ToStopSpinlockWatchdog = true;
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
}

// We only reach here if we've caught an exception
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
}

void PresetMan::SpinlockWatchdogThreadFunction(std::stop_token st, std::atomic<int>& mainThreadHeartbeat, std::atomic<bool>& spinlockDetected) {
int previousHeartbeat = 0;
while (!st.stop_requested()) {
if (m_ToStopSpinlockWatchdog) {
return;
}

std::unique_lock lk(m_SpinlockWdMutex);
m_SpinlockWdCv.wait_for(lk, std::chrono::milliseconds(1000), [&] {
return st.stop_requested();
});
if (st.stop_requested()) {
return;
}

int newHeartbeat = mainThreadHeartbeat;
if (newHeartbeat == previousHeartbeat) {
// Spinlock, report!
spinlockDetected = true;
m_ProgressDisplayCv.notify_all();
return;
}
previousHeartbeat = newHeartbeat;
}
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Source/Main.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -443,7 +443,7 @@ int main(int argc, char** argv) {

HandleMainArgs(argc, argv);

g_PresetMan.LoadAllDataModules();
g_PresetMan.LoadAllDataModules(PollSDLEvents);

if (!System::IsInExternalModuleValidationMode()) {
// Load the different input device icons. This can't be done during UInputMan::Create() because the icon presets don't exist so we need to do this after modules are loaded.
Expand Down
2 changes: 1 addition & 1 deletion Source/Managers/ActivityMan.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -352,7 +352,7 @@ bool ActivityMan::LoadAndLaunchGame(const std::string& fileName) {

buffer[info.uncompressed_size] = 0; // null terminate

Reader reader(std::make_unique<std::istringstream>(buffer), filePath + "/Save.ini", true, nullptr, false);
Reader reader(std::make_unique<std::istringstream>(buffer), filePath + "/Save.ini", true, false);

std::unique_ptr<Scene> scene(std::make_unique<Scene>());
std::unique_ptr<GAScripted> activity(std::make_unique<GAScripted>());
Expand Down
299 changes: 253 additions & 46 deletions Source/Managers/PresetMan.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ void PresetMan::Destroy() {
Clear();
}

bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, bool userdata, const ProgressCallback& progressCallback) {
bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, bool userdata) {
if (moduleName.empty()) {
return false;
}
Expand All@@ -87,9 +87,6 @@ bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, boo

// Official modules are stacked in the beginning of the vector.
if (official && !userdata) {
// Halt if an official module is being loaded after any non-official ones!
// RTEAssert(m_pDataModules.size() == m_OfficialModuleCount, "Trying to load an official module after a non-official one has been loaded!");

// Find where the official modules end in the vector.
std::vector<DataModule*>::iterator moduleItr = m_pDataModules.begin();
size_t newModuleID = 0;
Expand All@@ -108,72 +105,124 @@ bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, boo
m_DataModuleIDs.try_emplace(lowercaseName, m_pDataModules.size() - 1);
}

if (newModule->Create(moduleName, progressCallback) < 0) {
if (newModule->Create(moduleName) < 0) {
RTEAbort("Failed to find the " + moduleName + " Data Module!");
return false;
}
newModule = nullptr;
return true;
}

bool PresetMan::LoadAllDataModules() {
auto moduleLoadTimerStart = std::chrono::steady_clock::now();
bool PresetMan::LoadAllDataModules(std::function<void()> PollSDLEventsCallback) {
auto timerTotalFunctionStart = std::chrono::steady_clock::now();
std::chrono::milliseconds moduleLoadElapsedTime = {};

// Destroy any possible loaded modules
Destroy();

FindAndExtractZippedModules();

// Load all the official modules first!
for (const std::string& officialModule: c_OfficialModules) {
if (!LoadDataModule(officialModule, true, false, LoadingScreen::LoadingSplashProgressReport)) {
return false;
}
// No callback passing by a trickle, instead shove needed
// functions into statics of classes that will use them
DataModule::AssertFromWorkerAndShutdownAll = AssertFromModuleLoadingWorkerAndShutdownAll;
Reader::AssertFromWorkerAndShutdownAll = AssertFromModuleLoadingWorkerAndShutdownAll;
if (!g_SettingsMan.GetLoadingScreenProgressReportDisabled()) {
Reader::PushToProgressDisplayQueue = PushToProgressDisplayQueue;
DataModule::PushToProgressDisplayQueue = PushToProgressDisplayQueue;
}

// If a single module is specified, skip loading all other unofficial modules and load specified module only.
if (!m_SingleModuleToLoad.empty() && !IsModuleOfficial(m_SingleModuleToLoad)) {
if (!LoadDataModule(m_SingleModuleToLoad, false, false, LoadingScreen::LoadingSplashProgressReport)) {
g_ConsoleMan.PrintString("ERROR: Failed to load DataModule \"" + m_SingleModuleToLoad + "\"! Only official modules were loaded!");
return false;
}
} else {
std::vector<std::filesystem::directory_entry> modDirectoryFolders;
const std::string modDirectory = System::GetWorkingDirectory() + System::GetModDirectory();
std::copy_if(std::filesystem::directory_iterator(modDirectory), std::filesystem::directory_iterator(), std::back_inserter(modDirectoryFolders),
[](auto dirEntry) { return std::filesystem::is_directory(dirEntry); });
std::sort(modDirectoryFolders.begin(), modDirectoryFolders.end());

for (const std::filesystem::directory_entry& directoryEntry: modDirectoryFolders) {
std::string directoryEntryPath = directoryEntry.path().generic_string();
if (directoryEntryPath.ends_with(".rte")) {
std::string moduleName = directoryEntryPath.substr(directoryEntryPath.find_last_of('/') + 1, std::string::npos);
if (!g_SettingsMan.IsModDisabled(moduleName) && !IsModuleOfficial(moduleName) && !IsModuleUserdata(moduleName)) {
int moduleID = GetModuleID(moduleName);
// NOTE: LoadDataModule can return false (especially since it may try to load already loaded modules, which is okay) and shouldn't cause stop, so we can ignore its return value here.
if (moduleID < 0 || moduleID >= GetOfficialModuleCount()) {
LoadDataModule(moduleName, false, false, LoadingScreen::LoadingSplashProgressReport);
// Module loading Thread
std::atomic<ModuleLoadResult> loadingDone = ModuleLoadResult::StillWorking;
bool toDoProgressPrintOut = !g_SettingsMan.GetLoadingScreenProgressReportDisabled();
std::jthread moduleLoadingThread([&](std::stop_token st) {
ModuleLoadingThreadFunction(st, loadingDone, moduleLoadElapsedTime);
});

// Spinlock watchdog thread
std::atomic<int> mainThreadHeartbeat = 0;
std::atomic<bool> spinlockDetected = false;
std::jthread spinlockWatchdogThread([&](std::stop_token st) {
SpinlockWatchdogThreadFunction(st, mainThreadHeartbeat, spinlockDetected);
});

// Main thread - we drain display queue and poll SDL events
//
// We do this so the window does not freeze, and to handle
// the close button on the window being hit / Alt+F4 pressed
{
while (1) {
mainThreadHeartbeat++;
PollSDLEventsCallback();
if (true) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for the if (true). you can just make a code block, that's still local scope.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A very non-production PR from me, sorry! This and other things i intended not to have left in. Will fix up, thank you for great feedback!

ProgressDisplayEntry entry;
{
std::unique_lock lk(m_ProgressDisplayMutex);

// Sleep until there is work to do or 16 ms pass
m_ProgressDisplayCv.wait_for(lk, std::chrono::milliseconds(16), [&] {
return
!m_ProgressDisplayDeque.empty()
|| loadingDone != ModuleLoadResult::StillWorking
|| m_WorkerFailed
|| spinlockDetected
|| System::IsSetToQuit();
});

if (System::IsSetToQuit()
|| loadingDone == ModuleLoadResult::Failure
|| m_WorkerFailed)
{
moduleLoadingThread.request_stop();
break;
}

if (loadingDone == ModuleLoadResult::Success
&& m_ProgressDisplayDeque.empty())
{
break;
}

if (!m_ProgressDisplayDeque.empty()) {
entry = std::move(m_ProgressDisplayDeque.front());
m_ProgressDisplayDeque.pop_front();
} else if (spinlockDetected) {
spinlockDetected = false;
SpinlockAssert(toDoProgressPrintOut, loadingDone);
RTEAssert(false, to_string(mainThreadHeartbeat))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw we have RTEAbort for this usecase

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I learn more things 🙂
Will use!

}
}
LoadingScreen::LoadingSplashProgressReport(entry.first, entry.second);
}
}
}

// Load userdata modules AFTER all other techs etc are loaded; might be referring to stuff in user mods.
for (const auto& [userdataModuleName, userdataModuleFriendlyName]: c_UserdataModules) {
if (!std::filesystem::exists(System::GetWorkingDirectory() + System::GetUserdataDirectory() + userdataModuleName)) {
bool scanContentsAndIgnoreMissing = userdataModuleName == c_UserScenesModuleName;
DataModule::CreateOnDiskAsUserdata(userdataModuleName, userdataModuleFriendlyName, scanContentsAndIgnoreMissing, scanContentsAndIgnoreMissing);
}
if (!LoadDataModule(userdataModuleName, false, true, LoadingScreen::LoadingSplashProgressReport)) {
return false;
}
}
moduleLoadingThread.join();
spinlockWatchdogThread.request_stop();
m_SpinlockWdCv.notify_all();
spinlockWatchdogThread.join();

if (System::IsSetToQuit()) {
return false;
}

if (loadingDone == ModuleLoadResult::Failure) {
RTEAssert(false, m_WorkerErrorMessage);
}

// Compile the shaders we've deferred
for (auto* shader: m_ShadersToCompile) {
shader->Create();
}

if (g_SettingsMan.IsMeasuringModuleLoadTime()) {
std::chrono::milliseconds moduleLoadElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - moduleLoadTimerStart);
g_ConsoleMan.PrintString("Module load duration is: " + std::to_string(moduleLoadElapsedTime.count()) + "ms");
std::chrono::milliseconds totalFunctionElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - timerTotalFunctionStart);
std::string coutString = "Total loading time was " + std::to_string(totalFunctionElapsedTime.count()) + "ms";
if (!g_SettingsMan.GetLoadingScreenProgressReportDisabled()) {
coutString += " (module load duration: " + std::to_string(moduleLoadElapsedTime.count()) + "ms)";
}
g_ConsoleMan.PrintString(coutString);
}

return true;
}

Expand DownExpand Up@@ -350,6 +399,9 @@ const Entity* PresetMan::GetEntityPreset(Reader& reader) {
// Load class name and then preset instance
reader >> ClassName;
pClass = Entity::ClassInfo::GetClass(ClassName);
if (ClassName == "Shader") {
int a = 5;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???? (though if you really need this empty check to happen use (void); instead)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh this was to set a breakpoint on it 😅

}

if (pClass && pClass->IsConcrete()) {
// Instantiate
Expand DownExpand Up@@ -919,3 +971,158 @@ void PresetMan::FindAndExtractZippedModules() const {
}
}
}

void PresetMan::DeferShaderCompilationToBeDoneOnMainThread(Shader* shader) {
m_ShadersToCompile.push_back(shader);
}

void PresetMan::PushToProgressDisplayQueue(const std::string& string, bool newItem) {
{
std::lock_guard lg(g_PresetMan.m_ProgressDisplayMutex);
g_PresetMan.m_ProgressDisplayDeque.push_back({string, newItem});
}
g_PresetMan.m_ProgressDisplayCv.notify_one();
}

void PresetMan::AssertFromModuleLoadingWorkerAndShutdownAll(const std::string& assertString) {
g_PresetMan.m_WorkerFailed = true;
g_PresetMan.m_ProgressDisplayCv.notify_all();
}

void PresetMan::SpinlockAssert(bool toDoProgressPrintOut, ModuleLoadResult loadingDone) {

auto loadingDoneValueString = [](auto& loadingDone) -> const std::string {
switch (loadingDone) {
case ModuleLoadResult::Failure:
return "Failure";
case ModuleLoadResult::Success:
return "Success";
case ModuleLoadResult::StillWorking:
return "StillWorking";
default:
return "Unknown";
}
};
std::string assertString =
"Main thread spinlock during module loading!\n"
+ std::string("toDoProgressPrintOut = ") + std::to_string(toDoProgressPrintOut)
+ std::string("\nloadingDone = ") + loadingDoneValueString(loadingDone)
+ std::string("\nm_WorkerFailed = ") + std::to_string(m_WorkerFailed)
+ std::string("\nm_ProgressDisplayDeque.size() = ") + std::to_string(m_ProgressDisplayDeque.size());
RTEAssert(false, assertString);
}

void PresetMan::ModuleLoadingThreadFunction(std::stop_token st, std::atomic<ModuleLoadResult>& loadingDone, std::chrono::milliseconds& moduleLoadElapsedTime) {
try {
auto timerModuleLoadingThreadStart = std::chrono::steady_clock::now();
// auto ProgressCallback = g_SettingsMan.GetLoadingScreenProgressReportDisabled()
// ? nullptr
// : PresetMan::PushToProgressDisplayQueue;
// Load all the official modules first!
for (const std::string& officialModule: c_OfficialModules) {
if (st.stop_requested()) {
return;
}
if (!LoadDataModule(officialModule, true, false)) {
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
}

// If a single module is specified, skip loading all other unofficial modules and load specified module only.
if (!m_SingleModuleToLoad.empty() && !IsModuleOfficial(m_SingleModuleToLoad)) {
if (!LoadDataModule(m_SingleModuleToLoad, false, false)) {
g_ConsoleMan.PrintString("ERROR: Failed to load DataModule \"" + m_SingleModuleToLoad + "\"! Only official modules were loaded!");
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
} else {
std::vector<std::filesystem::directory_entry> modDirectoryFolders;
const std::string modDirectory = System::GetWorkingDirectory() + System::GetModDirectory();
std::copy_if(std::filesystem::directory_iterator(modDirectory), std::filesystem::directory_iterator(), std::back_inserter(modDirectoryFolders),
[](auto dirEntry) { return std::filesystem::is_directory(dirEntry); });
std::sort(modDirectoryFolders.begin(), modDirectoryFolders.end());

for (const std::filesystem::directory_entry& directoryEntry: modDirectoryFolders) {
if (st.stop_requested()) {
return;
}
std::string directoryEntryPath = directoryEntry.path().generic_string();
if (directoryEntryPath.ends_with(".rte")) {
std::string moduleName = directoryEntryPath.substr(directoryEntryPath.find_last_of('/') + 1, std::string::npos);
if (!g_SettingsMan.IsModDisabled(moduleName) && !IsModuleOfficial(moduleName) && !IsModuleUserdata(moduleName)) {
int moduleID = GetModuleID(moduleName);
// NOTE: LoadDataModule can return false (especially since it may try to load already loaded modules, which is okay) and shouldn't cause stop, so we can ignore its return value here.
if (moduleID < 0 || moduleID >= GetOfficialModuleCount()) {
LoadDataModule(moduleName, false, false);
}
}
}
}

// Load userdata modules AFTER all other techs etc are loaded; might be referring to stuff in user mods.
for (const auto& [userdataModuleName, userdataModuleFriendlyName]: c_UserdataModules) {
if (st.stop_requested()) {
return;
}
if (!std::filesystem::exists(System::GetWorkingDirectory() + System::GetUserdataDirectory() + userdataModuleName)) {
bool scanContentsAndIgnoreMissing = userdataModuleName == c_UserScenesModuleName;
DataModule::CreateOnDiskAsUserdata(userdataModuleName, userdataModuleFriendlyName, scanContentsAndIgnoreMissing, scanContentsAndIgnoreMissing);
}
if (!LoadDataModule(userdataModuleName, false, true)) {
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
}
}

moduleLoadElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - timerModuleLoadingThreadStart);

loadingDone = ModuleLoadResult::Success;
m_ProgressDisplayCv.notify_all();
return;
} catch (const std::exception& e) {
AssertFromModuleLoadingWorkerAndShutdownAll(std::string("Module loader exception!\n") + e.what());
m_ToStopSpinlockWatchdog = true;
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
} catch (...) {
AssertFromModuleLoadingWorkerAndShutdownAll("Module loader unknown exception!");
m_ToStopSpinlockWatchdog = true;
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
}

// We only reach here if we've caught an exception
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
}

void PresetMan::SpinlockWatchdogThreadFunction(std::stop_token st, std::atomic<int>& mainThreadHeartbeat, std::atomic<bool>& spinlockDetected) {
int previousHeartbeat = 0;
while (!st.stop_requested()) {
if (m_ToStopSpinlockWatchdog) {
return;
}

std::unique_lock lk(m_SpinlockWdMutex);
m_SpinlockWdCv.wait_for(lk, std::chrono::milliseconds(1000), [&] {
return st.stop_requested();
});
if (st.stop_requested()) {
return;
}

int newHeartbeat = mainThreadHeartbeat;
if (newHeartbeat == previousHeartbeat) {
// Spinlock, report!
spinlockDetected = true;
m_ProgressDisplayCv.notify_all();
return;
}
previousHeartbeat = newHeartbeat;
}
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Source/Main.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -443,7 +443,7 @@ int main(int argc, char** argv) {

HandleMainArgs(argc, argv);

g_PresetMan.LoadAllDataModules();
g_PresetMan.LoadAllDataModules(PollSDLEvents);

if (!System::IsInExternalModuleValidationMode()) {
// Load the different input device icons. This can't be done during UInputMan::Create() because the icon presets don't exist so we need to do this after modules are loaded.
Expand Down
2 changes: 1 addition & 1 deletion Source/Managers/ActivityMan.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -352,7 +352,7 @@ bool ActivityMan::LoadAndLaunchGame(const std::string& fileName) {

buffer[info.uncompressed_size] = 0; // null terminate

Reader reader(std::make_unique<std::istringstream>(buffer), filePath + "/Save.ini", true, nullptr, false);
Reader reader(std::make_unique<std::istringstream>(buffer), filePath + "/Save.ini", true, false);

std::unique_ptr<Scene> scene(std::make_unique<Scene>());
std::unique_ptr<GAScripted> activity(std::make_unique<GAScripted>());
Expand Down
299 changes: 253 additions & 46 deletions Source/Managers/PresetMan.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ void PresetMan::Destroy() {
Clear();
}

bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, bool userdata, const ProgressCallback& progressCallback) {
bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, bool userdata) {
if (moduleName.empty()) {
return false;
}
Expand All@@ -87,9 +87,6 @@ bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, boo

// Official modules are stacked in the beginning of the vector.
if (official && !userdata) {
// Halt if an official module is being loaded after any non-official ones!
// RTEAssert(m_pDataModules.size() == m_OfficialModuleCount, "Trying to load an official module after a non-official one has been loaded!");

// Find where the official modules end in the vector.
std::vector<DataModule*>::iterator moduleItr = m_pDataModules.begin();
size_t newModuleID = 0;
Expand All@@ -108,72 +105,124 @@ bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, boo
m_DataModuleIDs.try_emplace(lowercaseName, m_pDataModules.size() - 1);
}

if (newModule->Create(moduleName, progressCallback) < 0) {
if (newModule->Create(moduleName) < 0) {
RTEAbort("Failed to find the " + moduleName + " Data Module!");
return false;
}
newModule = nullptr;
return true;
}

bool PresetMan::LoadAllDataModules() {
auto moduleLoadTimerStart = std::chrono::steady_clock::now();
bool PresetMan::LoadAllDataModules(std::function<void()> PollSDLEventsCallback) {
auto timerTotalFunctionStart = std::chrono::steady_clock::now();
std::chrono::milliseconds moduleLoadElapsedTime = {};

// Destroy any possible loaded modules
Destroy();

FindAndExtractZippedModules();

// Load all the official modules first!
for (const std::string& officialModule: c_OfficialModules) {
if (!LoadDataModule(officialModule, true, false, LoadingScreen::LoadingSplashProgressReport)) {
return false;
}
// No callback passing by a trickle, instead shove needed
// functions into statics of classes that will use them
DataModule::AssertFromWorkerAndShutdownAll = AssertFromModuleLoadingWorkerAndShutdownAll;
Reader::AssertFromWorkerAndShutdownAll = AssertFromModuleLoadingWorkerAndShutdownAll;
if (!g_SettingsMan.GetLoadingScreenProgressReportDisabled()) {
Reader::PushToProgressDisplayQueue = PushToProgressDisplayQueue;
DataModule::PushToProgressDisplayQueue = PushToProgressDisplayQueue;
}

// If a single module is specified, skip loading all other unofficial modules and load specified module only.
if (!m_SingleModuleToLoad.empty() && !IsModuleOfficial(m_SingleModuleToLoad)) {
if (!LoadDataModule(m_SingleModuleToLoad, false, false, LoadingScreen::LoadingSplashProgressReport)) {
g_ConsoleMan.PrintString("ERROR: Failed to load DataModule \"" + m_SingleModuleToLoad + "\"! Only official modules were loaded!");
return false;
}
} else {
std::vector<std::filesystem::directory_entry> modDirectoryFolders;
const std::string modDirectory = System::GetWorkingDirectory() + System::GetModDirectory();
std::copy_if(std::filesystem::directory_iterator(modDirectory), std::filesystem::directory_iterator(), std::back_inserter(modDirectoryFolders),
[](auto dirEntry) { return std::filesystem::is_directory(dirEntry); });
std::sort(modDirectoryFolders.begin(), modDirectoryFolders.end());

for (const std::filesystem::directory_entry& directoryEntry: modDirectoryFolders) {
std::string directoryEntryPath = directoryEntry.path().generic_string();
if (directoryEntryPath.ends_with(".rte")) {
std::string moduleName = directoryEntryPath.substr(directoryEntryPath.find_last_of('/') + 1, std::string::npos);
if (!g_SettingsMan.IsModDisabled(moduleName) && !IsModuleOfficial(moduleName) && !IsModuleUserdata(moduleName)) {
int moduleID = GetModuleID(moduleName);
// NOTE: LoadDataModule can return false (especially since it may try to load already loaded modules, which is okay) and shouldn't cause stop, so we can ignore its return value here.
if (moduleID < 0 || moduleID >= GetOfficialModuleCount()) {
LoadDataModule(moduleName, false, false, LoadingScreen::LoadingSplashProgressReport);
// Module loading Thread
std::atomic<ModuleLoadResult> loadingDone = ModuleLoadResult::StillWorking;
bool toDoProgressPrintOut = !g_SettingsMan.GetLoadingScreenProgressReportDisabled();
std::jthread moduleLoadingThread([&](std::stop_token st) {
ModuleLoadingThreadFunction(st, loadingDone, moduleLoadElapsedTime);
});

// Spinlock watchdog thread
std::atomic<int> mainThreadHeartbeat = 0;
std::atomic<bool> spinlockDetected = false;
std::jthread spinlockWatchdogThread([&](std::stop_token st) {
SpinlockWatchdogThreadFunction(st, mainThreadHeartbeat, spinlockDetected);
});

// Main thread - we drain display queue and poll SDL events
//
// We do this so the window does not freeze, and to handle
// the close button on the window being hit / Alt+F4 pressed
{
while (1) {
mainThreadHeartbeat++;
PollSDLEventsCallback();
if (true) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for the if (true). you can just make a code block, that's still local scope.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A very non-production PR from me, sorry! This and other things i intended not to have left in. Will fix up, thank you for great feedback!

ProgressDisplayEntry entry;
{
std::unique_lock lk(m_ProgressDisplayMutex);

// Sleep until there is work to do or 16 ms pass
m_ProgressDisplayCv.wait_for(lk, std::chrono::milliseconds(16), [&] {
return
!m_ProgressDisplayDeque.empty()
|| loadingDone != ModuleLoadResult::StillWorking
|| m_WorkerFailed
|| spinlockDetected
|| System::IsSetToQuit();
});

if (System::IsSetToQuit()
|| loadingDone == ModuleLoadResult::Failure
|| m_WorkerFailed)
{
moduleLoadingThread.request_stop();
break;
}

if (loadingDone == ModuleLoadResult::Success
&& m_ProgressDisplayDeque.empty())
{
break;
}

if (!m_ProgressDisplayDeque.empty()) {
entry = std::move(m_ProgressDisplayDeque.front());
m_ProgressDisplayDeque.pop_front();
} else if (spinlockDetected) {
spinlockDetected = false;
SpinlockAssert(toDoProgressPrintOut, loadingDone);
RTEAssert(false, to_string(mainThreadHeartbeat))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw we have RTEAbort for this usecase

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I learn more things 🙂
Will use!

}
}
LoadingScreen::LoadingSplashProgressReport(entry.first, entry.second);
}
}
}

// Load userdata modules AFTER all other techs etc are loaded; might be referring to stuff in user mods.
for (const auto& [userdataModuleName, userdataModuleFriendlyName]: c_UserdataModules) {
if (!std::filesystem::exists(System::GetWorkingDirectory() + System::GetUserdataDirectory() + userdataModuleName)) {
bool scanContentsAndIgnoreMissing = userdataModuleName == c_UserScenesModuleName;
DataModule::CreateOnDiskAsUserdata(userdataModuleName, userdataModuleFriendlyName, scanContentsAndIgnoreMissing, scanContentsAndIgnoreMissing);
}
if (!LoadDataModule(userdataModuleName, false, true, LoadingScreen::LoadingSplashProgressReport)) {
return false;
}
}
moduleLoadingThread.join();
spinlockWatchdogThread.request_stop();
m_SpinlockWdCv.notify_all();
spinlockWatchdogThread.join();

if (System::IsSetToQuit()) {
return false;
}

if (loadingDone == ModuleLoadResult::Failure) {
RTEAssert(false, m_WorkerErrorMessage);
}

// Compile the shaders we've deferred
for (auto* shader: m_ShadersToCompile) {
shader->Create();
}

if (g_SettingsMan.IsMeasuringModuleLoadTime()) {
std::chrono::milliseconds moduleLoadElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - moduleLoadTimerStart);
g_ConsoleMan.PrintString("Module load duration is: " + std::to_string(moduleLoadElapsedTime.count()) + "ms");
std::chrono::milliseconds totalFunctionElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - timerTotalFunctionStart);
std::string coutString = "Total loading time was " + std::to_string(totalFunctionElapsedTime.count()) + "ms";
if (!g_SettingsMan.GetLoadingScreenProgressReportDisabled()) {
coutString += " (module load duration: " + std::to_string(moduleLoadElapsedTime.count()) + "ms)";
}
g_ConsoleMan.PrintString(coutString);
}

return true;
}

Expand DownExpand Up@@ -350,6 +399,9 @@ const Entity* PresetMan::GetEntityPreset(Reader& reader) {
// Load class name and then preset instance
reader >> ClassName;
pClass = Entity::ClassInfo::GetClass(ClassName);
if (ClassName == "Shader") {
int a = 5;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???? (though if you really need this empty check to happen use (void); instead)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh this was to set a breakpoint on it 😅

}

if (pClass && pClass->IsConcrete()) {
// Instantiate
Expand DownExpand Up@@ -919,3 +971,158 @@ void PresetMan::FindAndExtractZippedModules() const {
}
}
}

void PresetMan::DeferShaderCompilationToBeDoneOnMainThread(Shader* shader) {
m_ShadersToCompile.push_back(shader);
}

void PresetMan::PushToProgressDisplayQueue(const std::string& string, bool newItem) {
{
std::lock_guard lg(g_PresetMan.m_ProgressDisplayMutex);
g_PresetMan.m_ProgressDisplayDeque.push_back({string, newItem});
}
g_PresetMan.m_ProgressDisplayCv.notify_one();
}

void PresetMan::AssertFromModuleLoadingWorkerAndShutdownAll(const std::string& assertString) {
g_PresetMan.m_WorkerFailed = true;
g_PresetMan.m_ProgressDisplayCv.notify_all();
}

void PresetMan::SpinlockAssert(bool toDoProgressPrintOut, ModuleLoadResult loadingDone) {

auto loadingDoneValueString = [](auto& loadingDone) -> const std::string {
switch (loadingDone) {
case ModuleLoadResult::Failure:
return "Failure";
case ModuleLoadResult::Success:
return "Success";
case ModuleLoadResult::StillWorking:
return "StillWorking";
default:
return "Unknown";
}
};
std::string assertString =
"Main thread spinlock during module loading!\n"
+ std::string("toDoProgressPrintOut = ") + std::to_string(toDoProgressPrintOut)
+ std::string("\nloadingDone = ") + loadingDoneValueString(loadingDone)
+ std::string("\nm_WorkerFailed = ") + std::to_string(m_WorkerFailed)
+ std::string("\nm_ProgressDisplayDeque.size() = ") + std::to_string(m_ProgressDisplayDeque.size());
RTEAssert(false, assertString);
}

void PresetMan::ModuleLoadingThreadFunction(std::stop_token st, std::atomic<ModuleLoadResult>& loadingDone, std::chrono::milliseconds& moduleLoadElapsedTime) {
try {
auto timerModuleLoadingThreadStart = std::chrono::steady_clock::now();
// auto ProgressCallback = g_SettingsMan.GetLoadingScreenProgressReportDisabled()
// ? nullptr
// : PresetMan::PushToProgressDisplayQueue;
// Load all the official modules first!
for (const std::string& officialModule: c_OfficialModules) {
if (st.stop_requested()) {
return;
}
if (!LoadDataModule(officialModule, true, false)) {
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
}

// If a single module is specified, skip loading all other unofficial modules and load specified module only.
if (!m_SingleModuleToLoad.empty() && !IsModuleOfficial(m_SingleModuleToLoad)) {
if (!LoadDataModule(m_SingleModuleToLoad, false, false)) {
g_ConsoleMan.PrintString("ERROR: Failed to load DataModule \"" + m_SingleModuleToLoad + "\"! Only official modules were loaded!");
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
} else {
std::vector<std::filesystem::directory_entry> modDirectoryFolders;
const std::string modDirectory = System::GetWorkingDirectory() + System::GetModDirectory();
std::copy_if(std::filesystem::directory_iterator(modDirectory), std::filesystem::directory_iterator(), std::back_inserter(modDirectoryFolders),
[](auto dirEntry) { return std::filesystem::is_directory(dirEntry); });
std::sort(modDirectoryFolders.begin(), modDirectoryFolders.end());

for (const std::filesystem::directory_entry& directoryEntry: modDirectoryFolders) {
if (st.stop_requested()) {
return;
}
std::string directoryEntryPath = directoryEntry.path().generic_string();
if (directoryEntryPath.ends_with(".rte")) {
std::string moduleName = directoryEntryPath.substr(directoryEntryPath.find_last_of('/') + 1, std::string::npos);
if (!g_SettingsMan.IsModDisabled(moduleName) && !IsModuleOfficial(moduleName) && !IsModuleUserdata(moduleName)) {
int moduleID = GetModuleID(moduleName);
// NOTE: LoadDataModule can return false (especially since it may try to load already loaded modules, which is okay) and shouldn't cause stop, so we can ignore its return value here.
if (moduleID < 0 || moduleID >= GetOfficialModuleCount()) {
LoadDataModule(moduleName, false, false);
}
}
}
}

// Load userdata modules AFTER all other techs etc are loaded; might be referring to stuff in user mods.
for (const auto& [userdataModuleName, userdataModuleFriendlyName]: c_UserdataModules) {
if (st.stop_requested()) {
return;
}
if (!std::filesystem::exists(System::GetWorkingDirectory() + System::GetUserdataDirectory() + userdataModuleName)) {
bool scanContentsAndIgnoreMissing = userdataModuleName == c_UserScenesModuleName;
DataModule::CreateOnDiskAsUserdata(userdataModuleName, userdataModuleFriendlyName, scanContentsAndIgnoreMissing, scanContentsAndIgnoreMissing);
}
if (!LoadDataModule(userdataModuleName, false, true)) {
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
}
}

moduleLoadElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - timerModuleLoadingThreadStart);

loadingDone = ModuleLoadResult::Success;
m_ProgressDisplayCv.notify_all();
return;
} catch (const std::exception& e) {
AssertFromModuleLoadingWorkerAndShutdownAll(std::string("Module loader exception!\n") + e.what());
m_ToStopSpinlockWatchdog = true;
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
} catch (...) {
AssertFromModuleLoadingWorkerAndShutdownAll("Module loader unknown exception!");
m_ToStopSpinlockWatchdog = true;
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
}

// We only reach here if we've caught an exception
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
}

void PresetMan::SpinlockWatchdogThreadFunction(std::stop_token st, std::atomic<int>& mainThreadHeartbeat, std::atomic<bool>& spinlockDetected) {
int previousHeartbeat = 0;
while (!st.stop_requested()) {
if (m_ToStopSpinlockWatchdog) {
return;
}

std::unique_lock lk(m_SpinlockWdMutex);
m_SpinlockWdCv.wait_for(lk, std::chrono::milliseconds(1000), [&] {
return st.stop_requested();
});
if (st.stop_requested()) {
return;
}

int newHeartbeat = mainThreadHeartbeat;
if (newHeartbeat == previousHeartbeat) {
// Spinlock, report!
spinlockDetected = true;
m_ProgressDisplayCv.notify_all();
return;
}
previousHeartbeat = newHeartbeat;
}
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Source/Main.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -443,7 +443,7 @@ int main(int argc, char** argv) {

HandleMainArgs(argc, argv);

g_PresetMan.LoadAllDataModules();
g_PresetMan.LoadAllDataModules(PollSDLEvents);

if (!System::IsInExternalModuleValidationMode()) {
// Load the different input device icons. This can't be done during UInputMan::Create() because the icon presets don't exist so we need to do this after modules are loaded.
Expand Down
2 changes: 1 addition & 1 deletion Source/Managers/ActivityMan.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -352,7 +352,7 @@ bool ActivityMan::LoadAndLaunchGame(const std::string& fileName) {

buffer[info.uncompressed_size] = 0; // null terminate

Reader reader(std::make_unique<std::istringstream>(buffer), filePath + "/Save.ini", true, nullptr, false);
Reader reader(std::make_unique<std::istringstream>(buffer), filePath + "/Save.ini", true, false);

std::unique_ptr<Scene> scene(std::make_unique<Scene>());
std::unique_ptr<GAScripted> activity(std::make_unique<GAScripted>());
Expand Down
299 changes: 253 additions & 46 deletions Source/Managers/PresetMan.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ void PresetMan::Destroy() {
Clear();
}

bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, bool userdata, const ProgressCallback& progressCallback) {
bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, bool userdata) {
if (moduleName.empty()) {
return false;
}
Expand All@@ -87,9 +87,6 @@ bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, boo

// Official modules are stacked in the beginning of the vector.
if (official && !userdata) {
// Halt if an official module is being loaded after any non-official ones!
// RTEAssert(m_pDataModules.size() == m_OfficialModuleCount, "Trying to load an official module after a non-official one has been loaded!");

// Find where the official modules end in the vector.
std::vector<DataModule*>::iterator moduleItr = m_pDataModules.begin();
size_t newModuleID = 0;
Expand All@@ -108,72 +105,124 @@ bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, boo
m_DataModuleIDs.try_emplace(lowercaseName, m_pDataModules.size() - 1);
}

if (newModule->Create(moduleName, progressCallback) < 0) {
if (newModule->Create(moduleName) < 0) {
RTEAbort("Failed to find the " + moduleName + " Data Module!");
return false;
}
newModule = nullptr;
return true;
}

bool PresetMan::LoadAllDataModules() {
auto moduleLoadTimerStart = std::chrono::steady_clock::now();
bool PresetMan::LoadAllDataModules(std::function<void()> PollSDLEventsCallback) {
auto timerTotalFunctionStart = std::chrono::steady_clock::now();
std::chrono::milliseconds moduleLoadElapsedTime = {};

// Destroy any possible loaded modules
Destroy();

FindAndExtractZippedModules();

// Load all the official modules first!
for (const std::string& officialModule: c_OfficialModules) {
if (!LoadDataModule(officialModule, true, false, LoadingScreen::LoadingSplashProgressReport)) {
return false;
}
// No callback passing by a trickle, instead shove needed
// functions into statics of classes that will use them
DataModule::AssertFromWorkerAndShutdownAll = AssertFromModuleLoadingWorkerAndShutdownAll;
Reader::AssertFromWorkerAndShutdownAll = AssertFromModuleLoadingWorkerAndShutdownAll;
if (!g_SettingsMan.GetLoadingScreenProgressReportDisabled()) {
Reader::PushToProgressDisplayQueue = PushToProgressDisplayQueue;
DataModule::PushToProgressDisplayQueue = PushToProgressDisplayQueue;
}

// If a single module is specified, skip loading all other unofficial modules and load specified module only.
if (!m_SingleModuleToLoad.empty() && !IsModuleOfficial(m_SingleModuleToLoad)) {
if (!LoadDataModule(m_SingleModuleToLoad, false, false, LoadingScreen::LoadingSplashProgressReport)) {
g_ConsoleMan.PrintString("ERROR: Failed to load DataModule \"" + m_SingleModuleToLoad + "\"! Only official modules were loaded!");
return false;
}
} else {
std::vector<std::filesystem::directory_entry> modDirectoryFolders;
const std::string modDirectory = System::GetWorkingDirectory() + System::GetModDirectory();
std::copy_if(std::filesystem::directory_iterator(modDirectory), std::filesystem::directory_iterator(), std::back_inserter(modDirectoryFolders),
[](auto dirEntry) { return std::filesystem::is_directory(dirEntry); });
std::sort(modDirectoryFolders.begin(), modDirectoryFolders.end());

for (const std::filesystem::directory_entry& directoryEntry: modDirectoryFolders) {
std::string directoryEntryPath = directoryEntry.path().generic_string();
if (directoryEntryPath.ends_with(".rte")) {
std::string moduleName = directoryEntryPath.substr(directoryEntryPath.find_last_of('/') + 1, std::string::npos);
if (!g_SettingsMan.IsModDisabled(moduleName) && !IsModuleOfficial(moduleName) && !IsModuleUserdata(moduleName)) {
int moduleID = GetModuleID(moduleName);
// NOTE: LoadDataModule can return false (especially since it may try to load already loaded modules, which is okay) and shouldn't cause stop, so we can ignore its return value here.
if (moduleID < 0 || moduleID >= GetOfficialModuleCount()) {
LoadDataModule(moduleName, false, false, LoadingScreen::LoadingSplashProgressReport);
// Module loading Thread
std::atomic<ModuleLoadResult> loadingDone = ModuleLoadResult::StillWorking;
bool toDoProgressPrintOut = !g_SettingsMan.GetLoadingScreenProgressReportDisabled();
std::jthread moduleLoadingThread([&](std::stop_token st) {
ModuleLoadingThreadFunction(st, loadingDone, moduleLoadElapsedTime);
});

// Spinlock watchdog thread
std::atomic<int> mainThreadHeartbeat = 0;
std::atomic<bool> spinlockDetected = false;
std::jthread spinlockWatchdogThread([&](std::stop_token st) {
SpinlockWatchdogThreadFunction(st, mainThreadHeartbeat, spinlockDetected);
});

// Main thread - we drain display queue and poll SDL events
//
// We do this so the window does not freeze, and to handle
// the close button on the window being hit / Alt+F4 pressed
{
while (1) {
mainThreadHeartbeat++;
PollSDLEventsCallback();
if (true) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for the if (true). you can just make a code block, that's still local scope.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A very non-production PR from me, sorry! This and other things i intended not to have left in. Will fix up, thank you for great feedback!

ProgressDisplayEntry entry;
{
std::unique_lock lk(m_ProgressDisplayMutex);

// Sleep until there is work to do or 16 ms pass
m_ProgressDisplayCv.wait_for(lk, std::chrono::milliseconds(16), [&] {
return
!m_ProgressDisplayDeque.empty()
|| loadingDone != ModuleLoadResult::StillWorking
|| m_WorkerFailed
|| spinlockDetected
|| System::IsSetToQuit();
});

if (System::IsSetToQuit()
|| loadingDone == ModuleLoadResult::Failure
|| m_WorkerFailed)
{
moduleLoadingThread.request_stop();
break;
}

if (loadingDone == ModuleLoadResult::Success
&& m_ProgressDisplayDeque.empty())
{
break;
}

if (!m_ProgressDisplayDeque.empty()) {
entry = std::move(m_ProgressDisplayDeque.front());
m_ProgressDisplayDeque.pop_front();
} else if (spinlockDetected) {
spinlockDetected = false;
SpinlockAssert(toDoProgressPrintOut, loadingDone);
RTEAssert(false, to_string(mainThreadHeartbeat))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw we have RTEAbort for this usecase

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I learn more things 🙂
Will use!

}
}
LoadingScreen::LoadingSplashProgressReport(entry.first, entry.second);
}
}
}

// Load userdata modules AFTER all other techs etc are loaded; might be referring to stuff in user mods.
for (const auto& [userdataModuleName, userdataModuleFriendlyName]: c_UserdataModules) {
if (!std::filesystem::exists(System::GetWorkingDirectory() + System::GetUserdataDirectory() + userdataModuleName)) {
bool scanContentsAndIgnoreMissing = userdataModuleName == c_UserScenesModuleName;
DataModule::CreateOnDiskAsUserdata(userdataModuleName, userdataModuleFriendlyName, scanContentsAndIgnoreMissing, scanContentsAndIgnoreMissing);
}
if (!LoadDataModule(userdataModuleName, false, true, LoadingScreen::LoadingSplashProgressReport)) {
return false;
}
}
moduleLoadingThread.join();
spinlockWatchdogThread.request_stop();
m_SpinlockWdCv.notify_all();
spinlockWatchdogThread.join();

if (System::IsSetToQuit()) {
return false;
}

if (loadingDone == ModuleLoadResult::Failure) {
RTEAssert(false, m_WorkerErrorMessage);
}

// Compile the shaders we've deferred
for (auto* shader: m_ShadersToCompile) {
shader->Create();
}

if (g_SettingsMan.IsMeasuringModuleLoadTime()) {
std::chrono::milliseconds moduleLoadElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - moduleLoadTimerStart);
g_ConsoleMan.PrintString("Module load duration is: " + std::to_string(moduleLoadElapsedTime.count()) + "ms");
std::chrono::milliseconds totalFunctionElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - timerTotalFunctionStart);
std::string coutString = "Total loading time was " + std::to_string(totalFunctionElapsedTime.count()) + "ms";
if (!g_SettingsMan.GetLoadingScreenProgressReportDisabled()) {
coutString += " (module load duration: " + std::to_string(moduleLoadElapsedTime.count()) + "ms)";
}
g_ConsoleMan.PrintString(coutString);
}

return true;
}

Expand DownExpand Up@@ -350,6 +399,9 @@ const Entity* PresetMan::GetEntityPreset(Reader& reader) {
// Load class name and then preset instance
reader >> ClassName;
pClass = Entity::ClassInfo::GetClass(ClassName);
if (ClassName == "Shader") {
int a = 5;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???? (though if you really need this empty check to happen use (void); instead)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh this was to set a breakpoint on it 😅

}

if (pClass && pClass->IsConcrete()) {
// Instantiate
Expand DownExpand Up@@ -919,3 +971,158 @@ void PresetMan::FindAndExtractZippedModules() const {
}
}
}

void PresetMan::DeferShaderCompilationToBeDoneOnMainThread(Shader* shader) {
m_ShadersToCompile.push_back(shader);
}

void PresetMan::PushToProgressDisplayQueue(const std::string& string, bool newItem) {
{
std::lock_guard lg(g_PresetMan.m_ProgressDisplayMutex);
g_PresetMan.m_ProgressDisplayDeque.push_back({string, newItem});
}
g_PresetMan.m_ProgressDisplayCv.notify_one();
}

void PresetMan::AssertFromModuleLoadingWorkerAndShutdownAll(const std::string& assertString) {
g_PresetMan.m_WorkerFailed = true;
g_PresetMan.m_ProgressDisplayCv.notify_all();
}

void PresetMan::SpinlockAssert(bool toDoProgressPrintOut, ModuleLoadResult loadingDone) {

auto loadingDoneValueString = [](auto& loadingDone) -> const std::string {
switch (loadingDone) {
case ModuleLoadResult::Failure:
return "Failure";
case ModuleLoadResult::Success:
return "Success";
case ModuleLoadResult::StillWorking:
return "StillWorking";
default:
return "Unknown";
}
};
std::string assertString =
"Main thread spinlock during module loading!\n"
+ std::string("toDoProgressPrintOut = ") + std::to_string(toDoProgressPrintOut)
+ std::string("\nloadingDone = ") + loadingDoneValueString(loadingDone)
+ std::string("\nm_WorkerFailed = ") + std::to_string(m_WorkerFailed)
+ std::string("\nm_ProgressDisplayDeque.size() = ") + std::to_string(m_ProgressDisplayDeque.size());
RTEAssert(false, assertString);
}

void PresetMan::ModuleLoadingThreadFunction(std::stop_token st, std::atomic<ModuleLoadResult>& loadingDone, std::chrono::milliseconds& moduleLoadElapsedTime) {
try {
auto timerModuleLoadingThreadStart = std::chrono::steady_clock::now();
// auto ProgressCallback = g_SettingsMan.GetLoadingScreenProgressReportDisabled()
// ? nullptr
// : PresetMan::PushToProgressDisplayQueue;
// Load all the official modules first!
for (const std::string& officialModule: c_OfficialModules) {
if (st.stop_requested()) {
return;
}
if (!LoadDataModule(officialModule, true, false)) {
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
}

// If a single module is specified, skip loading all other unofficial modules and load specified module only.
if (!m_SingleModuleToLoad.empty() && !IsModuleOfficial(m_SingleModuleToLoad)) {
if (!LoadDataModule(m_SingleModuleToLoad, false, false)) {
g_ConsoleMan.PrintString("ERROR: Failed to load DataModule \"" + m_SingleModuleToLoad + "\"! Only official modules were loaded!");
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
} else {
std::vector<std::filesystem::directory_entry> modDirectoryFolders;
const std::string modDirectory = System::GetWorkingDirectory() + System::GetModDirectory();
std::copy_if(std::filesystem::directory_iterator(modDirectory), std::filesystem::directory_iterator(), std::back_inserter(modDirectoryFolders),
[](auto dirEntry) { return std::filesystem::is_directory(dirEntry); });
std::sort(modDirectoryFolders.begin(), modDirectoryFolders.end());

for (const std::filesystem::directory_entry& directoryEntry: modDirectoryFolders) {
if (st.stop_requested()) {
return;
}
std::string directoryEntryPath = directoryEntry.path().generic_string();
if (directoryEntryPath.ends_with(".rte")) {
std::string moduleName = directoryEntryPath.substr(directoryEntryPath.find_last_of('/') + 1, std::string::npos);
if (!g_SettingsMan.IsModDisabled(moduleName) && !IsModuleOfficial(moduleName) && !IsModuleUserdata(moduleName)) {
int moduleID = GetModuleID(moduleName);
// NOTE: LoadDataModule can return false (especially since it may try to load already loaded modules, which is okay) and shouldn't cause stop, so we can ignore its return value here.
if (moduleID < 0 || moduleID >= GetOfficialModuleCount()) {
LoadDataModule(moduleName, false, false);
}
}
}
}

// Load userdata modules AFTER all other techs etc are loaded; might be referring to stuff in user mods.
for (const auto& [userdataModuleName, userdataModuleFriendlyName]: c_UserdataModules) {
if (st.stop_requested()) {
return;
}
if (!std::filesystem::exists(System::GetWorkingDirectory() + System::GetUserdataDirectory() + userdataModuleName)) {
bool scanContentsAndIgnoreMissing = userdataModuleName == c_UserScenesModuleName;
DataModule::CreateOnDiskAsUserdata(userdataModuleName, userdataModuleFriendlyName, scanContentsAndIgnoreMissing, scanContentsAndIgnoreMissing);
}
if (!LoadDataModule(userdataModuleName, false, true)) {
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
}
}

moduleLoadElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - timerModuleLoadingThreadStart);

loadingDone = ModuleLoadResult::Success;
m_ProgressDisplayCv.notify_all();
return;
} catch (const std::exception& e) {
AssertFromModuleLoadingWorkerAndShutdownAll(std::string("Module loader exception!\n") + e.what());
m_ToStopSpinlockWatchdog = true;
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
} catch (...) {
AssertFromModuleLoadingWorkerAndShutdownAll("Module loader unknown exception!");
m_ToStopSpinlockWatchdog = true;
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
}

// We only reach here if we've caught an exception
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
}

void PresetMan::SpinlockWatchdogThreadFunction(std::stop_token st, std::atomic<int>& mainThreadHeartbeat, std::atomic<bool>& spinlockDetected) {
int previousHeartbeat = 0;
while (!st.stop_requested()) {
if (m_ToStopSpinlockWatchdog) {
return;
}

std::unique_lock lk(m_SpinlockWdMutex);
m_SpinlockWdCv.wait_for(lk, std::chrono::milliseconds(1000), [&] {
return st.stop_requested();
});
if (st.stop_requested()) {
return;
}

int newHeartbeat = mainThreadHeartbeat;
if (newHeartbeat == previousHeartbeat) {
// Spinlock, report!
spinlockDetected = true;
m_ProgressDisplayCv.notify_all();
return;
}
previousHeartbeat = newHeartbeat;
}
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Source/Main.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -443,7 +443,7 @@ int main(int argc, char** argv) {

HandleMainArgs(argc, argv);

g_PresetMan.LoadAllDataModules();
g_PresetMan.LoadAllDataModules(PollSDLEvents);

if (!System::IsInExternalModuleValidationMode()) {
// Load the different input device icons. This can't be done during UInputMan::Create() because the icon presets don't exist so we need to do this after modules are loaded.
Expand Down
2 changes: 1 addition & 1 deletion Source/Managers/ActivityMan.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -352,7 +352,7 @@ bool ActivityMan::LoadAndLaunchGame(const std::string& fileName) {

buffer[info.uncompressed_size] = 0; // null terminate

Reader reader(std::make_unique<std::istringstream>(buffer), filePath + "/Save.ini", true, nullptr, false);
Reader reader(std::make_unique<std::istringstream>(buffer), filePath + "/Save.ini", true, false);

std::unique_ptr<Scene> scene(std::make_unique<Scene>());
std::unique_ptr<GAScripted> activity(std::make_unique<GAScripted>());
Expand Down
299 changes: 253 additions & 46 deletions Source/Managers/PresetMan.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ void PresetMan::Destroy() {
Clear();
}

bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, bool userdata, const ProgressCallback& progressCallback) {
bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, bool userdata) {
if (moduleName.empty()) {
return false;
}
Expand All@@ -87,9 +87,6 @@ bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, boo

// Official modules are stacked in the beginning of the vector.
if (official && !userdata) {
// Halt if an official module is being loaded after any non-official ones!
// RTEAssert(m_pDataModules.size() == m_OfficialModuleCount, "Trying to load an official module after a non-official one has been loaded!");

// Find where the official modules end in the vector.
std::vector<DataModule*>::iterator moduleItr = m_pDataModules.begin();
size_t newModuleID = 0;
Expand All@@ -108,72 +105,124 @@ bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, boo
m_DataModuleIDs.try_emplace(lowercaseName, m_pDataModules.size() - 1);
}

if (newModule->Create(moduleName, progressCallback) < 0) {
if (newModule->Create(moduleName) < 0) {
RTEAbort("Failed to find the " + moduleName + " Data Module!");
return false;
}
newModule = nullptr;
return true;
}

bool PresetMan::LoadAllDataModules() {
auto moduleLoadTimerStart = std::chrono::steady_clock::now();
bool PresetMan::LoadAllDataModules(std::function<void()> PollSDLEventsCallback) {
auto timerTotalFunctionStart = std::chrono::steady_clock::now();
std::chrono::milliseconds moduleLoadElapsedTime = {};

// Destroy any possible loaded modules
Destroy();

FindAndExtractZippedModules();

// Load all the official modules first!
for (const std::string& officialModule: c_OfficialModules) {
if (!LoadDataModule(officialModule, true, false, LoadingScreen::LoadingSplashProgressReport)) {
return false;
}
// No callback passing by a trickle, instead shove needed
// functions into statics of classes that will use them
DataModule::AssertFromWorkerAndShutdownAll = AssertFromModuleLoadingWorkerAndShutdownAll;
Reader::AssertFromWorkerAndShutdownAll = AssertFromModuleLoadingWorkerAndShutdownAll;
if (!g_SettingsMan.GetLoadingScreenProgressReportDisabled()) {
Reader::PushToProgressDisplayQueue = PushToProgressDisplayQueue;
DataModule::PushToProgressDisplayQueue = PushToProgressDisplayQueue;
}

// If a single module is specified, skip loading all other unofficial modules and load specified module only.
if (!m_SingleModuleToLoad.empty() && !IsModuleOfficial(m_SingleModuleToLoad)) {
if (!LoadDataModule(m_SingleModuleToLoad, false, false, LoadingScreen::LoadingSplashProgressReport)) {
g_ConsoleMan.PrintString("ERROR: Failed to load DataModule \"" + m_SingleModuleToLoad + "\"! Only official modules were loaded!");
return false;
}
} else {
std::vector<std::filesystem::directory_entry> modDirectoryFolders;
const std::string modDirectory = System::GetWorkingDirectory() + System::GetModDirectory();
std::copy_if(std::filesystem::directory_iterator(modDirectory), std::filesystem::directory_iterator(), std::back_inserter(modDirectoryFolders),
[](auto dirEntry) { return std::filesystem::is_directory(dirEntry); });
std::sort(modDirectoryFolders.begin(), modDirectoryFolders.end());

for (const std::filesystem::directory_entry& directoryEntry: modDirectoryFolders) {
std::string directoryEntryPath = directoryEntry.path().generic_string();
if (directoryEntryPath.ends_with(".rte")) {
std::string moduleName = directoryEntryPath.substr(directoryEntryPath.find_last_of('/') + 1, std::string::npos);
if (!g_SettingsMan.IsModDisabled(moduleName) && !IsModuleOfficial(moduleName) && !IsModuleUserdata(moduleName)) {
int moduleID = GetModuleID(moduleName);
// NOTE: LoadDataModule can return false (especially since it may try to load already loaded modules, which is okay) and shouldn't cause stop, so we can ignore its return value here.
if (moduleID < 0 || moduleID >= GetOfficialModuleCount()) {
LoadDataModule(moduleName, false, false, LoadingScreen::LoadingSplashProgressReport);
// Module loading Thread
std::atomic<ModuleLoadResult> loadingDone = ModuleLoadResult::StillWorking;
bool toDoProgressPrintOut = !g_SettingsMan.GetLoadingScreenProgressReportDisabled();
std::jthread moduleLoadingThread([&](std::stop_token st) {
ModuleLoadingThreadFunction(st, loadingDone, moduleLoadElapsedTime);
});

// Spinlock watchdog thread
std::atomic<int> mainThreadHeartbeat = 0;
std::atomic<bool> spinlockDetected = false;
std::jthread spinlockWatchdogThread([&](std::stop_token st) {
SpinlockWatchdogThreadFunction(st, mainThreadHeartbeat, spinlockDetected);
});

// Main thread - we drain display queue and poll SDL events
//
// We do this so the window does not freeze, and to handle
// the close button on the window being hit / Alt+F4 pressed
{
while (1) {
mainThreadHeartbeat++;
PollSDLEventsCallback();
if (true) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for the if (true). you can just make a code block, that's still local scope.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A very non-production PR from me, sorry! This and other things i intended not to have left in. Will fix up, thank you for great feedback!

ProgressDisplayEntry entry;
{
std::unique_lock lk(m_ProgressDisplayMutex);

// Sleep until there is work to do or 16 ms pass
m_ProgressDisplayCv.wait_for(lk, std::chrono::milliseconds(16), [&] {
return
!m_ProgressDisplayDeque.empty()
|| loadingDone != ModuleLoadResult::StillWorking
|| m_WorkerFailed
|| spinlockDetected
|| System::IsSetToQuit();
});

if (System::IsSetToQuit()
|| loadingDone == ModuleLoadResult::Failure
|| m_WorkerFailed)
{
moduleLoadingThread.request_stop();
break;
}

if (loadingDone == ModuleLoadResult::Success
&& m_ProgressDisplayDeque.empty())
{
break;
}

if (!m_ProgressDisplayDeque.empty()) {
entry = std::move(m_ProgressDisplayDeque.front());
m_ProgressDisplayDeque.pop_front();
} else if (spinlockDetected) {
spinlockDetected = false;
SpinlockAssert(toDoProgressPrintOut, loadingDone);
RTEAssert(false, to_string(mainThreadHeartbeat))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw we have RTEAbort for this usecase

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I learn more things 🙂
Will use!

}
}
LoadingScreen::LoadingSplashProgressReport(entry.first, entry.second);
}
}
}

// Load userdata modules AFTER all other techs etc are loaded; might be referring to stuff in user mods.
for (const auto& [userdataModuleName, userdataModuleFriendlyName]: c_UserdataModules) {
if (!std::filesystem::exists(System::GetWorkingDirectory() + System::GetUserdataDirectory() + userdataModuleName)) {
bool scanContentsAndIgnoreMissing = userdataModuleName == c_UserScenesModuleName;
DataModule::CreateOnDiskAsUserdata(userdataModuleName, userdataModuleFriendlyName, scanContentsAndIgnoreMissing, scanContentsAndIgnoreMissing);
}
if (!LoadDataModule(userdataModuleName, false, true, LoadingScreen::LoadingSplashProgressReport)) {
return false;
}
}
moduleLoadingThread.join();
spinlockWatchdogThread.request_stop();
m_SpinlockWdCv.notify_all();
spinlockWatchdogThread.join();

if (System::IsSetToQuit()) {
return false;
}

if (loadingDone == ModuleLoadResult::Failure) {
RTEAssert(false, m_WorkerErrorMessage);
}

// Compile the shaders we've deferred
for (auto* shader: m_ShadersToCompile) {
shader->Create();
}

if (g_SettingsMan.IsMeasuringModuleLoadTime()) {
std::chrono::milliseconds moduleLoadElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - moduleLoadTimerStart);
g_ConsoleMan.PrintString("Module load duration is: " + std::to_string(moduleLoadElapsedTime.count()) + "ms");
std::chrono::milliseconds totalFunctionElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - timerTotalFunctionStart);
std::string coutString = "Total loading time was " + std::to_string(totalFunctionElapsedTime.count()) + "ms";
if (!g_SettingsMan.GetLoadingScreenProgressReportDisabled()) {
coutString += " (module load duration: " + std::to_string(moduleLoadElapsedTime.count()) + "ms)";
}
g_ConsoleMan.PrintString(coutString);
}

return true;
}

Expand DownExpand Up@@ -350,6 +399,9 @@ const Entity* PresetMan::GetEntityPreset(Reader& reader) {
// Load class name and then preset instance
reader >> ClassName;
pClass = Entity::ClassInfo::GetClass(ClassName);
if (ClassName == "Shader") {
int a = 5;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???? (though if you really need this empty check to happen use (void); instead)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh this was to set a breakpoint on it 😅

}

if (pClass && pClass->IsConcrete()) {
// Instantiate
Expand DownExpand Up@@ -919,3 +971,158 @@ void PresetMan::FindAndExtractZippedModules() const {
}
}
}

void PresetMan::DeferShaderCompilationToBeDoneOnMainThread(Shader* shader) {
m_ShadersToCompile.push_back(shader);
}

void PresetMan::PushToProgressDisplayQueue(const std::string& string, bool newItem) {
{
std::lock_guard lg(g_PresetMan.m_ProgressDisplayMutex);
g_PresetMan.m_ProgressDisplayDeque.push_back({string, newItem});
}
g_PresetMan.m_ProgressDisplayCv.notify_one();
}

void PresetMan::AssertFromModuleLoadingWorkerAndShutdownAll(const std::string& assertString) {
g_PresetMan.m_WorkerFailed = true;
g_PresetMan.m_ProgressDisplayCv.notify_all();
}

void PresetMan::SpinlockAssert(bool toDoProgressPrintOut, ModuleLoadResult loadingDone) {

auto loadingDoneValueString = [](auto& loadingDone) -> const std::string {
switch (loadingDone) {
case ModuleLoadResult::Failure:
return "Failure";
case ModuleLoadResult::Success:
return "Success";
case ModuleLoadResult::StillWorking:
return "StillWorking";
default:
return "Unknown";
}
};
std::string assertString =
"Main thread spinlock during module loading!\n"
+ std::string("toDoProgressPrintOut = ") + std::to_string(toDoProgressPrintOut)
+ std::string("\nloadingDone = ") + loadingDoneValueString(loadingDone)
+ std::string("\nm_WorkerFailed = ") + std::to_string(m_WorkerFailed)
+ std::string("\nm_ProgressDisplayDeque.size() = ") + std::to_string(m_ProgressDisplayDeque.size());
RTEAssert(false, assertString);
}

void PresetMan::ModuleLoadingThreadFunction(std::stop_token st, std::atomic<ModuleLoadResult>& loadingDone, std::chrono::milliseconds& moduleLoadElapsedTime) {
try {
auto timerModuleLoadingThreadStart = std::chrono::steady_clock::now();
// auto ProgressCallback = g_SettingsMan.GetLoadingScreenProgressReportDisabled()
// ? nullptr
// : PresetMan::PushToProgressDisplayQueue;
// Load all the official modules first!
for (const std::string& officialModule: c_OfficialModules) {
if (st.stop_requested()) {
return;
}
if (!LoadDataModule(officialModule, true, false)) {
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
}

// If a single module is specified, skip loading all other unofficial modules and load specified module only.
if (!m_SingleModuleToLoad.empty() && !IsModuleOfficial(m_SingleModuleToLoad)) {
if (!LoadDataModule(m_SingleModuleToLoad, false, false)) {
g_ConsoleMan.PrintString("ERROR: Failed to load DataModule \"" + m_SingleModuleToLoad + "\"! Only official modules were loaded!");
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
} else {
std::vector<std::filesystem::directory_entry> modDirectoryFolders;
const std::string modDirectory = System::GetWorkingDirectory() + System::GetModDirectory();
std::copy_if(std::filesystem::directory_iterator(modDirectory), std::filesystem::directory_iterator(), std::back_inserter(modDirectoryFolders),
[](auto dirEntry) { return std::filesystem::is_directory(dirEntry); });
std::sort(modDirectoryFolders.begin(), modDirectoryFolders.end());

for (const std::filesystem::directory_entry& directoryEntry: modDirectoryFolders) {
if (st.stop_requested()) {
return;
}
std::string directoryEntryPath = directoryEntry.path().generic_string();
if (directoryEntryPath.ends_with(".rte")) {
std::string moduleName = directoryEntryPath.substr(directoryEntryPath.find_last_of('/') + 1, std::string::npos);
if (!g_SettingsMan.IsModDisabled(moduleName) && !IsModuleOfficial(moduleName) && !IsModuleUserdata(moduleName)) {
int moduleID = GetModuleID(moduleName);
// NOTE: LoadDataModule can return false (especially since it may try to load already loaded modules, which is okay) and shouldn't cause stop, so we can ignore its return value here.
if (moduleID < 0 || moduleID >= GetOfficialModuleCount()) {
LoadDataModule(moduleName, false, false);
}
}
}
}

// Load userdata modules AFTER all other techs etc are loaded; might be referring to stuff in user mods.
for (const auto& [userdataModuleName, userdataModuleFriendlyName]: c_UserdataModules) {
if (st.stop_requested()) {
return;
}
if (!std::filesystem::exists(System::GetWorkingDirectory() + System::GetUserdataDirectory() + userdataModuleName)) {
bool scanContentsAndIgnoreMissing = userdataModuleName == c_UserScenesModuleName;
DataModule::CreateOnDiskAsUserdata(userdataModuleName, userdataModuleFriendlyName, scanContentsAndIgnoreMissing, scanContentsAndIgnoreMissing);
}
if (!LoadDataModule(userdataModuleName, false, true)) {
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
}
}

moduleLoadElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - timerModuleLoadingThreadStart);

loadingDone = ModuleLoadResult::Success;
m_ProgressDisplayCv.notify_all();
return;
} catch (const std::exception& e) {
AssertFromModuleLoadingWorkerAndShutdownAll(std::string("Module loader exception!\n") + e.what());
m_ToStopSpinlockWatchdog = true;
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
} catch (...) {
AssertFromModuleLoadingWorkerAndShutdownAll("Module loader unknown exception!");
m_ToStopSpinlockWatchdog = true;
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
}

// We only reach here if we've caught an exception
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
}

void PresetMan::SpinlockWatchdogThreadFunction(std::stop_token st, std::atomic<int>& mainThreadHeartbeat, std::atomic<bool>& spinlockDetected) {
int previousHeartbeat = 0;
while (!st.stop_requested()) {
if (m_ToStopSpinlockWatchdog) {
return;
}

std::unique_lock lk(m_SpinlockWdMutex);
m_SpinlockWdCv.wait_for(lk, std::chrono::milliseconds(1000), [&] {
return st.stop_requested();
});
if (st.stop_requested()) {
return;
}

int newHeartbeat = mainThreadHeartbeat;
if (newHeartbeat == previousHeartbeat) {
// Spinlock, report!
spinlockDetected = true;
m_ProgressDisplayCv.notify_all();
return;
}
previousHeartbeat = newHeartbeat;
}
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Source/Main.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -443,7 +443,7 @@ int main(int argc, char** argv) {

HandleMainArgs(argc, argv);

g_PresetMan.LoadAllDataModules();
g_PresetMan.LoadAllDataModules(PollSDLEvents);

if (!System::IsInExternalModuleValidationMode()) {
// Load the different input device icons. This can't be done during UInputMan::Create() because the icon presets don't exist so we need to do this after modules are loaded.
Expand Down
2 changes: 1 addition & 1 deletion Source/Managers/ActivityMan.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -352,7 +352,7 @@ bool ActivityMan::LoadAndLaunchGame(const std::string& fileName) {

buffer[info.uncompressed_size] = 0; // null terminate

Reader reader(std::make_unique<std::istringstream>(buffer), filePath + "/Save.ini", true, nullptr, false);
Reader reader(std::make_unique<std::istringstream>(buffer), filePath + "/Save.ini", true, false);

std::unique_ptr<Scene> scene(std::make_unique<Scene>());
std::unique_ptr<GAScripted> activity(std::make_unique<GAScripted>());
Expand Down
299 changes: 253 additions & 46 deletions Source/Managers/PresetMan.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -67,7 +67,7 @@ void PresetMan::Destroy() {
Clear();
}

bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, bool userdata, const ProgressCallback& progressCallback) {
bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, bool userdata) {
if (moduleName.empty()) {
return false;
}
Expand All@@ -87,9 +87,6 @@ bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, boo

// Official modules are stacked in the beginning of the vector.
if (official && !userdata) {
// Halt if an official module is being loaded after any non-official ones!
// RTEAssert(m_pDataModules.size() == m_OfficialModuleCount, "Trying to load an official module after a non-official one has been loaded!");

// Find where the official modules end in the vector.
std::vector<DataModule*>::iterator moduleItr = m_pDataModules.begin();
size_t newModuleID = 0;
Expand All@@ -108,72 +105,124 @@ bool PresetMan::LoadDataModule(const std::string& moduleName, bool official, boo
m_DataModuleIDs.try_emplace(lowercaseName, m_pDataModules.size() - 1);
}

if (newModule->Create(moduleName, progressCallback) < 0) {
if (newModule->Create(moduleName) < 0) {
RTEAbort("Failed to find the " + moduleName + " Data Module!");
return false;
}
newModule = nullptr;
return true;
}

bool PresetMan::LoadAllDataModules() {
auto moduleLoadTimerStart = std::chrono::steady_clock::now();
bool PresetMan::LoadAllDataModules(std::function<void()> PollSDLEventsCallback) {
auto timerTotalFunctionStart = std::chrono::steady_clock::now();
std::chrono::milliseconds moduleLoadElapsedTime = {};

// Destroy any possible loaded modules
Destroy();

FindAndExtractZippedModules();

// Load all the official modules first!
for (const std::string& officialModule: c_OfficialModules) {
if (!LoadDataModule(officialModule, true, false, LoadingScreen::LoadingSplashProgressReport)) {
return false;
}
// No callback passing by a trickle, instead shove needed
// functions into statics of classes that will use them
DataModule::AssertFromWorkerAndShutdownAll = AssertFromModuleLoadingWorkerAndShutdownAll;
Reader::AssertFromWorkerAndShutdownAll = AssertFromModuleLoadingWorkerAndShutdownAll;
if (!g_SettingsMan.GetLoadingScreenProgressReportDisabled()) {
Reader::PushToProgressDisplayQueue = PushToProgressDisplayQueue;
DataModule::PushToProgressDisplayQueue = PushToProgressDisplayQueue;
}

// If a single module is specified, skip loading all other unofficial modules and load specified module only.
if (!m_SingleModuleToLoad.empty() && !IsModuleOfficial(m_SingleModuleToLoad)) {
if (!LoadDataModule(m_SingleModuleToLoad, false, false, LoadingScreen::LoadingSplashProgressReport)) {
g_ConsoleMan.PrintString("ERROR: Failed to load DataModule \"" + m_SingleModuleToLoad + "\"! Only official modules were loaded!");
return false;
}
} else {
std::vector<std::filesystem::directory_entry> modDirectoryFolders;
const std::string modDirectory = System::GetWorkingDirectory() + System::GetModDirectory();
std::copy_if(std::filesystem::directory_iterator(modDirectory), std::filesystem::directory_iterator(), std::back_inserter(modDirectoryFolders),
[](auto dirEntry) { return std::filesystem::is_directory(dirEntry); });
std::sort(modDirectoryFolders.begin(), modDirectoryFolders.end());

for (const std::filesystem::directory_entry& directoryEntry: modDirectoryFolders) {
std::string directoryEntryPath = directoryEntry.path().generic_string();
if (directoryEntryPath.ends_with(".rte")) {
std::string moduleName = directoryEntryPath.substr(directoryEntryPath.find_last_of('/') + 1, std::string::npos);
if (!g_SettingsMan.IsModDisabled(moduleName) && !IsModuleOfficial(moduleName) && !IsModuleUserdata(moduleName)) {
int moduleID = GetModuleID(moduleName);
// NOTE: LoadDataModule can return false (especially since it may try to load already loaded modules, which is okay) and shouldn't cause stop, so we can ignore its return value here.
if (moduleID < 0 || moduleID >= GetOfficialModuleCount()) {
LoadDataModule(moduleName, false, false, LoadingScreen::LoadingSplashProgressReport);
// Module loading Thread
std::atomic<ModuleLoadResult> loadingDone = ModuleLoadResult::StillWorking;
bool toDoProgressPrintOut = !g_SettingsMan.GetLoadingScreenProgressReportDisabled();
std::jthread moduleLoadingThread([&](std::stop_token st) {
ModuleLoadingThreadFunction(st, loadingDone, moduleLoadElapsedTime);
});

// Spinlock watchdog thread
std::atomic<int> mainThreadHeartbeat = 0;
std::atomic<bool> spinlockDetected = false;
std::jthread spinlockWatchdogThread([&](std::stop_token st) {
SpinlockWatchdogThreadFunction(st, mainThreadHeartbeat, spinlockDetected);
});

// Main thread - we drain display queue and poll SDL events
//
// We do this so the window does not freeze, and to handle
// the close button on the window being hit / Alt+F4 pressed
{
while (1) {
mainThreadHeartbeat++;
PollSDLEventsCallback();
if (true) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for the if (true). you can just make a code block, that's still local scope.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A very non-production PR from me, sorry! This and other things i intended not to have left in. Will fix up, thank you for great feedback!

ProgressDisplayEntry entry;
{
std::unique_lock lk(m_ProgressDisplayMutex);

// Sleep until there is work to do or 16 ms pass
m_ProgressDisplayCv.wait_for(lk, std::chrono::milliseconds(16), [&] {
return
!m_ProgressDisplayDeque.empty()
|| loadingDone != ModuleLoadResult::StillWorking
|| m_WorkerFailed
|| spinlockDetected
|| System::IsSetToQuit();
});

if (System::IsSetToQuit()
|| loadingDone == ModuleLoadResult::Failure
|| m_WorkerFailed)
{
moduleLoadingThread.request_stop();
break;
}

if (loadingDone == ModuleLoadResult::Success
&& m_ProgressDisplayDeque.empty())
{
break;
}

if (!m_ProgressDisplayDeque.empty()) {
entry = std::move(m_ProgressDisplayDeque.front());
m_ProgressDisplayDeque.pop_front();
} else if (spinlockDetected) {
spinlockDetected = false;
SpinlockAssert(toDoProgressPrintOut, loadingDone);
RTEAssert(false, to_string(mainThreadHeartbeat))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw we have RTEAbort for this usecase

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I learn more things 🙂
Will use!

}
}
LoadingScreen::LoadingSplashProgressReport(entry.first, entry.second);
}
}
}

// Load userdata modules AFTER all other techs etc are loaded; might be referring to stuff in user mods.
for (const auto& [userdataModuleName, userdataModuleFriendlyName]: c_UserdataModules) {
if (!std::filesystem::exists(System::GetWorkingDirectory() + System::GetUserdataDirectory() + userdataModuleName)) {
bool scanContentsAndIgnoreMissing = userdataModuleName == c_UserScenesModuleName;
DataModule::CreateOnDiskAsUserdata(userdataModuleName, userdataModuleFriendlyName, scanContentsAndIgnoreMissing, scanContentsAndIgnoreMissing);
}
if (!LoadDataModule(userdataModuleName, false, true, LoadingScreen::LoadingSplashProgressReport)) {
return false;
}
}
moduleLoadingThread.join();
spinlockWatchdogThread.request_stop();
m_SpinlockWdCv.notify_all();
spinlockWatchdogThread.join();

if (System::IsSetToQuit()) {
return false;
}

if (loadingDone == ModuleLoadResult::Failure) {
RTEAssert(false, m_WorkerErrorMessage);
}

// Compile the shaders we've deferred
for (auto* shader: m_ShadersToCompile) {
shader->Create();
}

if (g_SettingsMan.IsMeasuringModuleLoadTime()) {
std::chrono::milliseconds moduleLoadElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - moduleLoadTimerStart);
g_ConsoleMan.PrintString("Module load duration is: " + std::to_string(moduleLoadElapsedTime.count()) + "ms");
std::chrono::milliseconds totalFunctionElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - timerTotalFunctionStart);
std::string coutString = "Total loading time was " + std::to_string(totalFunctionElapsedTime.count()) + "ms";
if (!g_SettingsMan.GetLoadingScreenProgressReportDisabled()) {
coutString += " (module load duration: " + std::to_string(moduleLoadElapsedTime.count()) + "ms)";
}
g_ConsoleMan.PrintString(coutString);
}

return true;
}

Expand DownExpand Up@@ -350,6 +399,9 @@ const Entity* PresetMan::GetEntityPreset(Reader& reader) {
// Load class name and then preset instance
reader >> ClassName;
pClass = Entity::ClassInfo::GetClass(ClassName);
if (ClassName == "Shader") {
int a = 5;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???? (though if you really need this empty check to happen use (void); instead)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh this was to set a breakpoint on it 😅

}

if (pClass && pClass->IsConcrete()) {
// Instantiate
Expand DownExpand Up@@ -919,3 +971,158 @@ void PresetMan::FindAndExtractZippedModules() const {
}
}
}

void PresetMan::DeferShaderCompilationToBeDoneOnMainThread(Shader* shader) {
m_ShadersToCompile.push_back(shader);
}

void PresetMan::PushToProgressDisplayQueue(const std::string& string, bool newItem) {
{
std::lock_guard lg(g_PresetMan.m_ProgressDisplayMutex);
g_PresetMan.m_ProgressDisplayDeque.push_back({string, newItem});
}
g_PresetMan.m_ProgressDisplayCv.notify_one();
}

void PresetMan::AssertFromModuleLoadingWorkerAndShutdownAll(const std::string& assertString) {
g_PresetMan.m_WorkerFailed = true;
g_PresetMan.m_ProgressDisplayCv.notify_all();
}

void PresetMan::SpinlockAssert(bool toDoProgressPrintOut, ModuleLoadResult loadingDone) {

auto loadingDoneValueString = [](auto& loadingDone) -> const std::string {
switch (loadingDone) {
case ModuleLoadResult::Failure:
return "Failure";
case ModuleLoadResult::Success:
return "Success";
case ModuleLoadResult::StillWorking:
return "StillWorking";
default:
return "Unknown";
}
};
std::string assertString =
"Main thread spinlock during module loading!\n"
+ std::string("toDoProgressPrintOut = ") + std::to_string(toDoProgressPrintOut)
+ std::string("\nloadingDone = ") + loadingDoneValueString(loadingDone)
+ std::string("\nm_WorkerFailed = ") + std::to_string(m_WorkerFailed)
+ std::string("\nm_ProgressDisplayDeque.size() = ") + std::to_string(m_ProgressDisplayDeque.size());
RTEAssert(false, assertString);
}

void PresetMan::ModuleLoadingThreadFunction(std::stop_token st, std::atomic<ModuleLoadResult>& loadingDone, std::chrono::milliseconds& moduleLoadElapsedTime) {
try {
auto timerModuleLoadingThreadStart = std::chrono::steady_clock::now();
// auto ProgressCallback = g_SettingsMan.GetLoadingScreenProgressReportDisabled()
// ? nullptr
// : PresetMan::PushToProgressDisplayQueue;
// Load all the official modules first!
for (const std::string& officialModule: c_OfficialModules) {
if (st.stop_requested()) {
return;
}
if (!LoadDataModule(officialModule, true, false)) {
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
}

// If a single module is specified, skip loading all other unofficial modules and load specified module only.
if (!m_SingleModuleToLoad.empty() && !IsModuleOfficial(m_SingleModuleToLoad)) {
if (!LoadDataModule(m_SingleModuleToLoad, false, false)) {
g_ConsoleMan.PrintString("ERROR: Failed to load DataModule \"" + m_SingleModuleToLoad + "\"! Only official modules were loaded!");
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
} else {
std::vector<std::filesystem::directory_entry> modDirectoryFolders;
const std::string modDirectory = System::GetWorkingDirectory() + System::GetModDirectory();
std::copy_if(std::filesystem::directory_iterator(modDirectory), std::filesystem::directory_iterator(), std::back_inserter(modDirectoryFolders),
[](auto dirEntry) { return std::filesystem::is_directory(dirEntry); });
std::sort(modDirectoryFolders.begin(), modDirectoryFolders.end());

for (const std::filesystem::directory_entry& directoryEntry: modDirectoryFolders) {
if (st.stop_requested()) {
return;
}
std::string directoryEntryPath = directoryEntry.path().generic_string();
if (directoryEntryPath.ends_with(".rte")) {
std::string moduleName = directoryEntryPath.substr(directoryEntryPath.find_last_of('/') + 1, std::string::npos);
if (!g_SettingsMan.IsModDisabled(moduleName) && !IsModuleOfficial(moduleName) && !IsModuleUserdata(moduleName)) {
int moduleID = GetModuleID(moduleName);
// NOTE: LoadDataModule can return false (especially since it may try to load already loaded modules, which is okay) and shouldn't cause stop, so we can ignore its return value here.
if (moduleID < 0 || moduleID >= GetOfficialModuleCount()) {
LoadDataModule(moduleName, false, false);
}
}
}
}

// Load userdata modules AFTER all other techs etc are loaded; might be referring to stuff in user mods.
for (const auto& [userdataModuleName, userdataModuleFriendlyName]: c_UserdataModules) {
if (st.stop_requested()) {
return;
}
if (!std::filesystem::exists(System::GetWorkingDirectory() + System::GetUserdataDirectory() + userdataModuleName)) {
bool scanContentsAndIgnoreMissing = userdataModuleName == c_UserScenesModuleName;
DataModule::CreateOnDiskAsUserdata(userdataModuleName, userdataModuleFriendlyName, scanContentsAndIgnoreMissing, scanContentsAndIgnoreMissing);
}
if (!LoadDataModule(userdataModuleName, false, true)) {
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
return;
}
}
}

moduleLoadElapsedTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - timerModuleLoadingThreadStart);

loadingDone = ModuleLoadResult::Success;
m_ProgressDisplayCv.notify_all();
return;
} catch (const std::exception& e) {
AssertFromModuleLoadingWorkerAndShutdownAll(std::string("Module loader exception!\n") + e.what());
m_ToStopSpinlockWatchdog = true;
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
} catch (...) {
AssertFromModuleLoadingWorkerAndShutdownAll("Module loader unknown exception!");
m_ToStopSpinlockWatchdog = true;
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
}

// We only reach here if we've caught an exception
loadingDone = ModuleLoadResult::Failure;
m_ProgressDisplayCv.notify_all();
}

void PresetMan::SpinlockWatchdogThreadFunction(std::stop_token st, std::atomic<int>& mainThreadHeartbeat, std::atomic<bool>& spinlockDetected) {
int previousHeartbeat = 0;
while (!st.stop_requested()) {
if (m_ToStopSpinlockWatchdog) {
return;
}

std::unique_lock lk(m_SpinlockWdMutex);
m_SpinlockWdCv.wait_for(lk, std::chrono::milliseconds(1000), [&] {
return st.stop_requested();
});
if (st.stop_requested()) {
return;
}

int newHeartbeat = mainThreadHeartbeat;
if (newHeartbeat == previousHeartbeat) {
// Spinlock, report!
spinlockDetected = true;
m_ProgressDisplayCv.notify_all();
return;
}
previousHeartbeat = newHeartbeat;
}
}
Loading
Loading