Jump to content

Recommended Posts

Hello,

I'm curious if anyone has managed to achieve a seamless hot reload experience with Python—similar to the hot reload feature available in Visual Studio 2022 for C++. Instead of having to close, package, and reopen the client each time, is there a way to update the code on the fly with Python? If so, how can I replicate this setup?

Link to comment
https://metin2.dev/topic/33456-how-to-hot-reload-python-on-metin2/
Share on other sites

  • Replies 2
  • Created
  • Last Reply

Top Posters In This Topic

Popular Days

Top Posters In This Topic

  • Active+ Member

here you can find a pasta example from the files I used which will help you understand the logic

 

Usage

Spoiler

enum EHotReloadType
{
	HOT_RELOAD_ROOT = 0,
	HOT_RELOAD_UISCRIPT,
	HOT_RELOAD_MAIN_FOLDER,
	HOT_RELOAD_PROTO,
	HOT_RELOAD_YMIRWORK,
	HOT_RELOAD_SPECIFIC_SCRIPT,
	HOT_RELOAD_MAX_NUM
};
std::array <DirectoryMonitor, HOT_RELOAD_MAX_NUM> g_HotReloadMonitors;

extern volatile bool g_vbCloseTriggered;
void FSWatcherEvent(DWORD action, const std::wstring& path)
{
	if (g_vbCloseTriggered)
		return;

	TraceError("File action: %d, Path: %ls", action, path.c_str());

	if (action == FILE_ACTION_MODIFIED)
	{
		TraceError("Hot reload: %ls", path.c_str());

		std::string srFile = std::string(path.begin(), path.end());

		// Check if the file extension is .py or .pyx
		auto stExtension = srFile.substr(srFile.find_last_of(".") + 1);
		if (stExtension == "py" || stExtension == "pyx")
		{
			// Split path
			auto stModuleName = srFile.substr(srFile.find_last_of("\\") + 1);
			stModuleName = stModuleName.substr(0, stModuleName.find_last_of("."));

			// Reload the module
			// CPythonLauncher::Instance().ReloadModule(stModuleName);

			// CPythonSystem& rkSystem = CPythonSystem::Instance();
			// PyCallClassMemberFunc(rkSystem.GetInterfaceHandler(), "ReloadModule", Py_BuildValue("(s)", stModuleName.c_str()));

			// Call "ReLoadModule" builtin method (def ReLoadModule(name))
			PyObject* pModule = PyImport_ImportModule("__builtin__");
			if (pModule)
			{
				PyObject* pFunc = PyObject_GetAttrString(pModule, "ReLoadModule");
				if (pFunc)
				{
					PyObject* pArgs = PyTuple_Pack(1, Py_BuildValue("s", stModuleName.c_str()));
					PyObject* pResult = PyObject_CallObject(pFunc, pArgs);

					Py_XDECREF(pArgs);
					Py_XDECREF(pResult);
					Py_XDECREF(pFunc);
				}
				Py_XDECREF(pModule);
			}
		}
		else if (srFile.ends_with("_proto.txt") || srFile.ends_with("_names.txt"))
		{
			// Reload the local files
			const auto stLocalePath = LocaleService_GetLocalePath();

			extern bool LoadLocaleData(const char* localePath);
			LoadLocaleData(stLocalePath);
		}
		else
		{
			TraceError("Hot reload: %ls, Unsupported file type yet", path.c_str());
		}
	}
}



void ImGuiManager::RenderHotReloadManager()
{

	if (g_bHotReloadRoot)
	{
		// Register monitor
		if (!g_HotReloadMonitors[HOT_RELOAD_ROOT].IsInitialized())
		{
			TraceError("Start monitoring root directory for hot reload");

			std::error_code ec;
			std::string stCwd = std::filesystem::current_path(ec).string();
			std::string stDirectoryToMonitor = fmt::format("{}/../../source/Client/.scripts/root", stCwd);
			stDirectoryToMonitor = std::filesystem::canonical(stDirectoryToMonitor, ec).string(); // resolve symlinks
			std::wstring wsDirectoryToMonitor = std::wstring(stDirectoryToMonitor.begin(), stDirectoryToMonitor.end());

			g_HotReloadMonitors[HOT_RELOAD_ROOT].StartMonitor(
				wsDirectoryToMonitor,
				true,
				FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE,
				FSWatcherEvent
			);
		}
	}
	else
	{
		// Stop monitoring
		if (g_HotReloadMonitors[HOT_RELOAD_ROOT].IsInitialized())
		{
			TraceError("Stop monitoring root directory for hot reload");

			g_HotReloadMonitors[HOT_RELOAD_ROOT].StopMonitor();
		}
	}

	if (g_bHotReloadUIScript)
	{
		// Register monitor
		if (!g_HotReloadMonitors[HOT_RELOAD_UISCRIPT].IsInitialized())
		{
			TraceError("Start monitoring UIScript directory for hot reload");

			std::error_code ec;
			std::string stCwd = std::filesystem::current_path(ec).string();
			std::string stDirectoryToMonitor = fmt::format("{}/pack/root/uiscript", stCwd);
			stDirectoryToMonitor = std::filesystem::canonical(stDirectoryToMonitor, ec).string(); // resolve symlinks
			std::wstring wsDirectoryToMonitor = std::wstring(stDirectoryToMonitor.begin(), stDirectoryToMonitor.end());

			g_HotReloadMonitors[HOT_RELOAD_UISCRIPT].StartMonitor(
				wsDirectoryToMonitor,
				true,
				FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE,
				FSWatcherEvent
			);
		}
	}
	else
	{
		// Stop monitoring
		if (g_HotReloadMonitors[HOT_RELOAD_UISCRIPT].IsInitialized())
		{
			TraceError("Stop monitoring UIScript directory for hot reload");

			g_HotReloadMonitors[HOT_RELOAD_UISCRIPT].StopMonitor();
		}
	}

	if (g_bHotReloadMainFolder)
	{
		// Register monitor
		if (!g_HotReloadMonitors[HOT_RELOAD_MAIN_FOLDER].IsInitialized())
		{
			TraceError("Start monitoring main folder for hot reload");

			std::error_code ec;
			std::string stCwd = std::filesystem::current_path(ec).string();
			std::wstring wsDirectoryToMonitor = std::wstring(stCwd.begin(), stCwd.end());

			g_HotReloadMonitors[HOT_RELOAD_MAIN_FOLDER].StartMonitor(
				wsDirectoryToMonitor,
				true,
				FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE,
				FSWatcherEvent
			);
		}
	}
	else
	{
		// Stop monitoring
		if (g_HotReloadMonitors[HOT_RELOAD_MAIN_FOLDER].IsInitialized())
		{
			TraceError("Stop monitoring main folder for hot reload");

			g_HotReloadMonitors[HOT_RELOAD_MAIN_FOLDER].StopMonitor();
		}
	}

	if (g_bHotReloadProto)
	{
		// Register monitor
		if (!g_HotReloadMonitors[HOT_RELOAD_PROTO].IsInitialized())
		{
			TraceError("Start monitoring proto directory for hot reload");

			std::error_code ec;
			std::string stCwd = std::filesystem::current_path(ec).string();
			std::string stDirectoryToMonitor = fmt::format("{}/../../server/srv1/share/conf", stCwd);
			stDirectoryToMonitor = std::filesystem::canonical(stDirectoryToMonitor, ec).string(); // resolve symlinks
			std::wstring wsDirectoryToMonitor = std::wstring(stDirectoryToMonitor.begin(), stDirectoryToMonitor.end());

			g_HotReloadMonitors[HOT_RELOAD_PROTO].StartMonitor(
				wsDirectoryToMonitor,
				true,
				FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE,
				FSWatcherEvent
			);
		}
	}
	else
	{
		// Stop monitoring
		if (g_HotReloadMonitors[HOT_RELOAD_PROTO].IsInitialized())
		{
			TraceError("Stop monitoring proto directory for hot reload");

			g_HotReloadMonitors[HOT_RELOAD_PROTO].StopMonitor();
		}
	}

	if (g_bHotReloadYmirWork)
	{
		// Register monitor
		if (!g_HotReloadMonitors[HOT_RELOAD_YMIRWORK].IsInitialized())
		{
			TraceError("Start monitoring Ymir Work directory for hot reload");

			std::wstring wsDirectoryToMonitor = L"d:\\ymir work";
			g_HotReloadMonitors[HOT_RELOAD_YMIRWORK].StartMonitor(
				wsDirectoryToMonitor,
				true,
				FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE,
				FSWatcherEvent
			);
		}
	}
	else
	{
		// Stop monitoring
		if (g_HotReloadMonitors[HOT_RELOAD_YMIRWORK].IsInitialized())
		{
			TraceError("Stop monitoring Ymir Work directory for hot reload");

			g_HotReloadMonitors[HOT_RELOAD_YMIRWORK].StopMonitor();
		}
	}

	if (g_bHotReloadSpecificScript)
	{
		if (ImGui::Begin("Hot Reload Specific Script", &g_bHotReloadSpecificScript))
		{
			static char szSpecificScript[1024 + 1];
			ImGui::InputTextMultiline("##SpecificScript", szSpecificScript, sizeof(szSpecificScript), ImVec2(100, 30), ImGuiInputTextFlags_AllowTabInput);

			if (ImGui::Button("Reload"))
			{
				std::string stModuleName = szSpecificScript;
				// split extension
				auto stExtension = stModuleName.substr(stModuleName.find_last_of(".") + 1);
				if (stExtension == "py" || stExtension == "pyx")
					stModuleName = stModuleName.substr(0, stModuleName.find_last_of("."));

				CPythonLauncher::Instance().ReloadModule(stModuleName);
			}
		}
		ImGui::End();
	}
}

 

 

DirectoryMonitor.h

Spoiler
#include <windows.h>
#include <future>
#include <string>
#include <memory>
#include <functional>
#include <iostream>

class DirectoryMonitor
{
	using ChangeNotify = std::function<void(DWORD, const std::wstring&)>;

public:
	// Constructor with optional existing handle support
	DirectoryMonitor(HANDLE existing_handle = INVALID_HANDLE_VALUE) :
		directory_(existing_handle)
	{
		::ZeroMemory(&overlapped_, sizeof(OVERLAPPED));
		overlapped_.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
	}
	~DirectoryMonitor()
	{
		StopMonitor();

		if (overlapped_.hEvent) 
		{
			CloseHandle(overlapped_.hEvent);
			overlapped_.hEvent = nullptr;
		}
	}

	// Expose the directory handle for external access
	HANDLE GetDirectoryHandle() const
	{
		return directory_;
	}

	// Set the directory handle from an external source
	void SetDirectoryHandle(HANDLE handle)
	{
		StopMonitor();
		directory_ = handle;
	}

	bool StartMonitor(const std::wstring& directory, bool watch_sub_dir, DWORD filter, ChangeNotify callback)
	{
		// If no valid handle is provided, create one
		if (!directory_ || directory_ == INVALID_HANDLE_VALUE)
		{
			std::wstring adjusted_directory = directory;
			if (!adjusted_directory.empty() && adjusted_directory.back() != L'\\')
				adjusted_directory += L"\\";

			CloseHandleIfValid(directory_);

			directory_ = ::CreateFileW(
				adjusted_directory.c_str(), FILE_LIST_DIRECTORY,
				FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 
				nullptr, OPEN_EXISTING,
				FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr
			);

			if (!directory_ || directory_ == INVALID_HANDLE_VALUE)
			{
				TraceError("CreateFileW(%ls) failed with error %d", adjusted_directory.c_str(), GetLastError());
				return false;
			}
		}

		constexpr int buffer_size = 1024;
		watch_buffer_ = std::make_unique<BYTE[]>(buffer_size);

		DWORD bytes_read = 0;
		BOOL success = ::ReadDirectoryChangesW(directory_, watch_buffer_.get(), buffer_size, watch_sub_dir, filter, &bytes_read, &overlapped_, nullptr);
		if (!success)
		{
			TraceError("ReadDirectoryChangesW(%ls) failed with error %d", directory.c_str(), GetLastError());
			CloseHandleIfValid(directory_);
			return false;
		}

		monitor_task_ = std::async(std::launch::async, [this, filter, callback, directory, watch_sub_dir]() {
			MonitorLoop(filter, callback, directory, watch_sub_dir);
		});
		if (!monitor_task_.valid())
		{
			TraceError("Failed to start monitor task");
			CloseHandleIfValid(directory_);
			return false;
		}

		initialized_ = true;
		return true;
	}

	void StopMonitor()
	{
		if (directory_ && directory_ != INVALID_HANDLE_VALUE)
		{
			CancelIo(directory_);
			CloseHandleIfValid(directory_);
		}

		/*
		if (monitor_task_.valid())
		{
			monitor_task_.wait();
		}
		*/

		initialized_ = false;
	}

	bool IsMonitoring() const
	{
		return monitor_task_.valid() && monitor_task_.wait_for(std::chrono::seconds(0)) != std::future_status::ready;
	}

	bool IsInitialized() const
	{
		return initialized_;
	}

protected:
	void MonitorLoop(DWORD filter, ChangeNotify callback, const std::wstring& directory, bool watch_sub_dir)
	{
		extern volatile bool g_vbCloseTriggered;

		while (true)
		{
			if (g_vbCloseTriggered)
				break;

			DWORD bytes_transferred = 0;
			if (!GetOverlappedResult(directory_, &overlapped_, &bytes_transferred, TRUE))
				break;

			BYTE* current = watch_buffer_.get();
			while (true)
			{
				if (g_vbCloseTriggered)
					break;

				auto* fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(current);
				if (callback)
				{
					std::wstring file_path(fni->FileName, fni->FileNameLength / sizeof(WCHAR));
					callback(fni->Action, directory + L"\\" + file_path);
				}

				if (fni->NextEntryOffset == 0)
					break;
				
				current += fni->NextEntryOffset;
			}

			DWORD bytes_read = 0;
			if (!::ReadDirectoryChangesW(directory_, watch_buffer_.get(), 1024, watch_sub_dir, filter, &bytes_read, &overlapped_, nullptr))
			{
				TraceError("[LOOP] ReadDirectoryChangesW(%ls) failed with error %d", directory.c_str(), GetLastError());
				break;
			}
		}
	}

	void CloseHandleIfValid(HANDLE& handle)
	{
		if (handle && handle != INVALID_HANDLE_VALUE)
		{
			CloseHandle(handle);
			handle = INVALID_HANDLE_VALUE;
		}
	}

private:
	bool initialized_{ false };
	HANDLE directory_{ INVALID_HANDLE_VALUE };
	OVERLAPPED overlapped_{};
	std::unique_ptr <BYTE[]> watch_buffer_{};
	std::future <void> monitor_task_{};
};

 

 

system.py

Spoiler
def ReLoadModule(name):
	dbg.TraceError("ReLoading module {0}".format(name))
    
	import app
	app.ChangeReloadLockState(True)
    
	if name in sys.modules: del sys.modules[name]
	if name in locals(): del locals()[name]
	if name in globals(): del globals()[name]

	# Open the module file
	module_file_path = "{}.pyx".format(name)
	module_file = open(module_file_path, 'rb')

	# Load the module from the file
	module = imp.load_module(name, module_file, module_file_path, ('.pyx', 'rb', imp.PY_SOURCE))

	if module not in locals():
		locals()[name] = module

	if module not in globals():
		globals()[name] = module
  
	app.ChangeReloadLockState(False)

	return module

__builtin__.ReLoadModule = ReLoadModule

 

1 hour ago, Koray said:

here you can find a pasta example from the files I used which will help you understand the logic

 

Usage

  Hide contents

enum EHotReloadType
{
	HOT_RELOAD_ROOT = 0,
	HOT_RELOAD_UISCRIPT,
	HOT_RELOAD_MAIN_FOLDER,
	HOT_RELOAD_PROTO,
	HOT_RELOAD_YMIRWORK,
	HOT_RELOAD_SPECIFIC_SCRIPT,
	HOT_RELOAD_MAX_NUM
};
std::array <DirectoryMonitor, HOT_RELOAD_MAX_NUM>
 

g_HotReloadMonitors; extern volatile bool g_vbCloseTriggered; void FSWatcherEvent(DWORD action, const std::wstring& path) { if (g_vbCloseTriggered) return; TraceError("File action: %d, Path: %ls", action, path.c_str()); if (action == FILE_ACTION_MODIFIED) { TraceError("Hot reload: %ls", path.c_str()); std::string srFile = std::string(path.begin(), path.end()); // Check if the file extension is .py or .pyx auto stExtension = srFile.substr(srFile.find_last_of(".") + 1); if (stExtension == "py" || stExtension == "pyx") { // Split path auto stModuleName = srFile.substr(srFile.find_last_of("\\") + 1); stModuleName = stModuleName.substr(0, stModuleName.find_last_of(".")); // Reload the module // CPythonLauncher::Instance().ReloadModule(stModuleName); // CPythonSystem& rkSystem = CPythonSystem::Instance(); // PyCallClassMemberFunc(rkSystem.GetInterfaceHandler(), "ReloadModule", Py_BuildValue("(s)", stModuleName.c_str())); // Call "ReLoadModule" builtin method (def ReLoadModule(name)) PyObject* pModule = PyImport_ImportModule("__builtin__"); if (pModule) { PyObject* pFunc = PyObject_GetAttrString(pModule, "ReLoadModule"); if (pFunc) { PyObject* pArgs = PyTuple_Pack(1, Py_BuildValue("s", stModuleName.c_str())); PyObject* pResult = PyObject_CallObject(pFunc, pArgs); Py_XDECREF(pArgs); Py_XDECREF(pResult); Py_XDECREF(pFunc); } Py_XDECREF(pModule); } } else if (srFile.ends_with("_proto.txt") || srFile.ends_with("_names.txt")) { // Reload the local files const auto stLocalePath = LocaleService_GetLocalePath(); extern bool LoadLocaleData(const char* localePath); LoadLocaleData(stLocalePath); } else { TraceError("Hot reload: %ls, Unsupported file type yet", path.c_str()); } } } void ImGuiManager::RenderHotReloadManager() { if (g_bHotReloadRoot) { // Register monitor if (!g_HotReloadMonitors[HOT_RELOAD_ROOT].IsInitialized()) { TraceError("Start monitoring root directory for hot reload"); std::error_code ec; std::string stCwd = std::filesystem::current_path(ec).string(); std::string stDirectoryToMonitor = fmt::format("{}/../../source/Client/.scripts/root", stCwd); stDirectoryToMonitor = std::filesystem::canonical(stDirectoryToMonitor, ec).string(); // resolve symlinks std::wstring wsDirectoryToMonitor = std::wstring(stDirectoryToMonitor.begin(), stDirectoryToMonitor.end()); g_HotReloadMonitors[HOT_RELOAD_ROOT].StartMonitor( wsDirectoryToMonitor, true, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE, FSWatcherEvent ); } } else { // Stop monitoring if (g_HotReloadMonitors[HOT_RELOAD_ROOT].IsInitialized()) { TraceError("Stop monitoring root directory for hot reload"); g_HotReloadMonitors[HOT_RELOAD_ROOT].StopMonitor(); } } if (g_bHotReloadUIScript) { // Register monitor if (!g_HotReloadMonitors[HOT_RELOAD_UISCRIPT].IsInitialized()) { TraceError("Start monitoring UIScript directory for hot reload"); std::error_code ec; std::string stCwd = std::filesystem::current_path(ec).string(); std::string stDirectoryToMonitor = fmt::format("{}/pack/root/uiscript", stCwd); stDirectoryToMonitor = std::filesystem::canonical(stDirectoryToMonitor, ec).string(); // resolve symlinks std::wstring wsDirectoryToMonitor = std::wstring(stDirectoryToMonitor.begin(), stDirectoryToMonitor.end()); g_HotReloadMonitors[HOT_RELOAD_UISCRIPT].StartMonitor( wsDirectoryToMonitor, true, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE, FSWatcherEvent ); } } else { // Stop monitoring if (g_HotReloadMonitors[HOT_RELOAD_UISCRIPT].IsInitialized()) { TraceError("Stop monitoring UIScript directory for hot reload"); g_HotReloadMonitors[HOT_RELOAD_UISCRIPT].StopMonitor(); } } if (g_bHotReloadMainFolder) { // Register monitor if (!g_HotReloadMonitors[HOT_RELOAD_MAIN_FOLDER].IsInitialized()) { TraceError("Start monitoring main folder for hot reload"); std::error_code ec; std::string stCwd = std::filesystem::current_path(ec).string(); std::wstring wsDirectoryToMonitor = std::wstring(stCwd.begin(), stCwd.end()); g_HotReloadMonitors[HOT_RELOAD_MAIN_FOLDER].StartMonitor( wsDirectoryToMonitor, true, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE, FSWatcherEvent ); } } else { // Stop monitoring if (g_HotReloadMonitors[HOT_RELOAD_MAIN_FOLDER].IsInitialized()) { TraceError("Stop monitoring main folder for hot reload"); g_HotReloadMonitors[HOT_RELOAD_MAIN_FOLDER].StopMonitor(); } } if (g_bHotReloadProto) { // Register monitor if (!g_HotReloadMonitors[HOT_RELOAD_PROTO].IsInitialized()) { TraceError("Start monitoring proto directory for hot reload"); std::error_code ec; std::string stCwd = std::filesystem::current_path(ec).string(); std::string stDirectoryToMonitor = fmt::format("{}/../../server/srv1/share/conf", stCwd); stDirectoryToMonitor = std::filesystem::canonical(stDirectoryToMonitor, ec).string(); // resolve symlinks std::wstring wsDirectoryToMonitor = std::wstring(stDirectoryToMonitor.begin(), stDirectoryToMonitor.end()); g_HotReloadMonitors[HOT_RELOAD_PROTO].StartMonitor( wsDirectoryToMonitor, true, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE, FSWatcherEvent ); } } else { // Stop monitoring if (g_HotReloadMonitors[HOT_RELOAD_PROTO].IsInitialized()) { TraceError("Stop monitoring proto directory for hot reload"); g_HotReloadMonitors[HOT_RELOAD_PROTO].StopMonitor(); } } if (g_bHotReloadYmirWork) { // Register monitor if (!g_HotReloadMonitors[HOT_RELOAD_YMIRWORK].IsInitialized()) { TraceError("Start monitoring Ymir Work directory for hot reload"); std::wstring wsDirectoryToMonitor = L"d:\\ymir work"; g_HotReloadMonitors[HOT_RELOAD_YMIRWORK].StartMonitor( wsDirectoryToMonitor, true, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE, FSWatcherEvent ); } } else { // Stop monitoring if (g_HotReloadMonitors[HOT_RELOAD_YMIRWORK].IsInitialized()) { TraceError("Stop monitoring Ymir Work directory for hot reload"); g_HotReloadMonitors[HOT_RELOAD_YMIRWORK].StopMonitor(); } } if (g_bHotReloadSpecificScript) { if (ImGui::Begin("Hot Reload Specific Script", &g_bHotReloadSpecificScript)) { static char szSpecificScript[1024 + 1]; ImGui::InputTextMultiline("##SpecificScript", szSpecificScript, sizeof(szSpecificScript), ImVec2(100, 30), ImGuiInputTextFlags_AllowTabInput); if (ImGui::Button("Reload")) { std::string stModuleName = szSpecificScript; // split extension auto stExtension = stModuleName.substr(stModuleName.find_last_of(".") + 1); if (stExtension == "py" || stExtension == "pyx") stModuleName = stModuleName.substr(0, stModuleName.find_last_of(".")); CPythonLauncher::Instance().ReloadModule(stModuleName); } } ImGui::End(); } }

 

 

DirectoryMonitor.h

  Hide contents
#include <windows.h>
#include <future>
#include <string>
#include <memory>
#include <functional>
#include <iostream>

class DirectoryMonitor
{
	using ChangeNotify = std::function<void(DWORD, const std::wstring&)>;

public:
	// Constructor with optional existing handle support
	DirectoryMonitor(HANDLE existing_handle = INVALID_HANDLE_VALUE) :
		directory_(existing_handle)
	{
		::ZeroMemory(&overlapped_, sizeof(OVERLAPPED));
		overlapped_.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
	}
	~DirectoryMonitor()
	{
		StopMonitor();

		if (overlapped_.hEvent) 
		{
			CloseHandle(overlapped_.hEvent);
			overlapped_.hEvent = nullptr;
		}
	}

	// Expose the directory handle for external access
	HANDLE GetDirectoryHandle() const
	{
		return directory_;
	}

	// Set the directory handle from an external source
	void SetDirectoryHandle(HANDLE handle)
	{
		StopMonitor();
		directory_ = handle;
	}

	bool StartMonitor(const std::wstring& directory, bool watch_sub_dir, DWORD filter, ChangeNotify callback)
	{
		// If no valid handle is provided, create one
		if (!directory_ || directory_ == INVALID_HANDLE_VALUE)
		{
			std::wstring adjusted_directory = directory;
			if (!adjusted_directory.empty() && adjusted_directory.back() != L'\\')
				adjusted_directory += L"\\";

			CloseHandleIfValid(directory_);

			directory_ = ::CreateFileW(
				adjusted_directory.c_str(), FILE_LIST_DIRECTORY,
				FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 
				nullptr, OPEN_EXISTING,
				FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr
			);

			if (!directory_ || directory_ == INVALID_HANDLE_VALUE)
			{
				TraceError("CreateFileW(%ls) failed with error %d", adjusted_directory.c_str(), GetLastError());
				return false;
			}
		}

		constexpr int buffer_size = 1024;
		watch_buffer_ = std::make_unique<BYTE[]>(buffer_size);

		DWORD bytes_read = 0;
		BOOL success = ::ReadDirectoryChangesW(directory_, watch_buffer_.get(), buffer_size, watch_sub_dir, filter, &bytes_read, &overlapped_, nullptr);
		if (!success)
		{
			TraceError("ReadDirectoryChangesW(%ls) failed with error %d", directory.c_str(), GetLastError());
			CloseHandleIfValid(directory_);
			return false;
		}

		monitor_task_ = std::async(std::launch::async, [this, filter, callback, directory, watch_sub_dir]() {
			MonitorLoop(filter, callback, directory, watch_sub_dir);
		});
		if (!monitor_task_.valid())
		{
			TraceError("Failed to start monitor task");
			CloseHandleIfValid(directory_);
			return false;
		}

		initialized_ = true;
		return true;
	}

	void StopMonitor()
	{
		if (directory_ && directory_ != INVALID_HANDLE_VALUE)
		{
			CancelIo(directory_);
			CloseHandleIfValid(directory_);
		}

		/*
		if (monitor_task_.valid())
		{
			monitor_task_.wait();
		}
		*/

		initialized_ = false;
	}

	bool IsMonitoring() const
	{
		return monitor_task_.valid() && monitor_task_.wait_for(std::chrono::seconds(0)) != std::future_status::ready;
	}

	bool IsInitialized() const
	{
		return initialized_;
	}

protected:
	void MonitorLoop(DWORD filter, ChangeNotify callback, const std::wstring& directory, bool watch_sub_dir)
	{
		extern volatile bool g_vbCloseTriggered;

		while (true)
		{
			if (g_vbCloseTriggered)
				break;

			DWORD bytes_transferred = 0;
			if (!GetOverlappedResult(directory_, &overlapped_, &bytes_transferred, TRUE))
				break;

			BYTE* current = watch_buffer_.get();
			while (true)
			{
				if (g_vbCloseTriggered)
					break;

				auto* fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(current);
				if (callback)
				{
					std::wstring file_path(fni->FileName, fni->FileNameLength / sizeof(WCHAR));
					callback(fni->Action, directory + L"\\" + file_path);
				}

				if (fni->NextEntryOffset == 0)
					break;
				
				current += fni->NextEntryOffset;
			}

			DWORD bytes_read = 0;
			if (!::ReadDirectoryChangesW(directory_, watch_buffer_.get(), 1024, watch_sub_dir, filter, &bytes_read, &overlapped_, nullptr))
			{
				TraceError("[LOOP] ReadDirectoryChangesW(%ls) failed with error %d", directory.c_str(), GetLastError());
				break;
			}
		}
	}

	void CloseHandleIfValid(HANDLE& handle)
	{
		if (handle && handle != INVALID_HANDLE_VALUE)
		{
			CloseHandle(handle);
			handle = INVALID_HANDLE_VALUE;
		}
	}

private:
	bool initialized_{ false };
	HANDLE directory_{ INVALID_HANDLE_VALUE };
	OVERLAPPED overlapped_{};
	std::unique_ptr <BYTE[]> watch_buffer_{};
	std::future <void> monitor_task_{};
};

 

 

system.py

  Hide contents
def ReLoadModule(name):
	dbg.TraceError("ReLoading module {0}".format(name))
    
	import app
	app.ChangeReloadLockState(True)
    
	if name in sys.modules: del sys.modules[name]
	if name in locals(): del locals()[name]
	if name in globals(): del globals()[name]

	# Open the module file
	module_file_path = "{}.pyx".format(name)
	module_file = open(module_file_path, 'rb')

	# Load the module from the file
	module = imp.load_module(name, module_file, module_file_path, ('.pyx', 'rb', imp.PY_SOURCE))

	if module not in locals():
		locals()[name] = module

	if module not in globals():
		globals()[name] = module
  
	app.ChangeReloadLockState(False)

	return module

__builtin__.ReLoadModule = ReLoadModule

 

Hello, 

thanks for your answer, but i feel a bit confused, could you example how this works?, how can I implement it and use it?

thanks

Don't use any images from : imgur, turkmmop, freakgamers, inforge, hizliresim... Or your content will be deleted without notice...
Use : https://metin2.download/media/add/

Please use https://metin2.download/ when uploading files smaller than 100MB, otherwise the approval will take longer due to manual upload.

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
×
×
  • Create New...

Important Information

Terms of Use / Privacy Policy / Guidelines / We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.