diff --git a/addons/source-python/packages/source-python/listeners/tick.py b/addons/source-python/packages/source-python/listeners/tick.py
index 333fef15e..b84eaff82 100644
--- a/addons/source-python/packages/source-python/listeners/tick.py
+++ b/addons/source-python/packages/source-python/listeners/tick.py
@@ -23,6 +23,14 @@
)
+# =============================================================================
+# >> FORWARD IMPORTS
+# =============================================================================
+# Source.Python Imports
+# Listeners
+from _listeners._tick import ThreadPoker
+
+
# =============================================================================
# >> ALL DECLARATION
# =============================================================================
@@ -31,6 +39,7 @@
'GameThread',
'Repeat',
'RepeatStatus',
+ 'ThreadPoker',
)
@@ -60,6 +69,10 @@ def _unload_instance(self):
f'Thread "{self.name}" ({self.ident}) from "{self._caller}" '
f'is running even though its plugin has been unloaded!')
+ def run(self, *args, **kwargs):
+ with ThreadPoker():
+ super().run(*args, **kwargs)
+
# =============================================================================
# >> DELAY CLASSES
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 65cd0411f..73c96fcb9 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -314,11 +314,14 @@ Set(SOURCEPYTHON_KEYVALUES_MODULE_SOURCES
# ------------------------------------------------------------------
Set(SOURCEPYTHON_LISTENERS_MODULE_HEADERS
core/modules/listeners/listeners_manager.h
+ core/modules/listeners/listeners_tick.h
)
Set(SOURCEPYTHON_LISTENERS_MODULE_SOURCES
core/modules/listeners/listeners_manager.cpp
core/modules/listeners/listeners_wrap.cpp
+ core/modules/listeners/listeners_tick.cpp
+ core/modules/listeners/listeners_tick_wrap.cpp
)
# ------------------------------------------------------------------
diff --git a/src/core/modules/listeners/listeners_tick.cpp b/src/core/modules/listeners/listeners_tick.cpp
new file mode 100644
index 000000000..9f345d8e4
--- /dev/null
+++ b/src/core/modules/listeners/listeners_tick.cpp
@@ -0,0 +1,68 @@
+/**
+* =============================================================================
+* Source Python
+* Copyright (C) 2012-2023 Source Python Development Team. All rights reserved.
+* =============================================================================
+*
+* This program is free software; you can redistribute it and/or modify it under
+* the terms of the GNU General Public License, version 3.0, as published by the
+* Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful, but WITHOUT
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+* details.
+*
+* You should have received a copy of the GNU General Public License along with
+* this program. If not, see .
+*
+* As a special exception, the Source Python Team gives you permission
+* to link the code of this program (as well as its derivative works) to
+* "Half-Life 2," the "Source Engine," and any Game MODs that run on software
+* by the Valve Corporation. You must obey the GNU General Public License in
+* all respects for all other code used. Additionally, the Source.Python
+* Development Team grants this exception to all derivative works.
+*/
+
+//-----------------------------------------------------------------------------
+// Includes.
+//-----------------------------------------------------------------------------
+#include "listeners_tick.h"
+
+
+//-----------------------------------------------------------------------------
+// CThreadPoker initialization.
+//-----------------------------------------------------------------------------
+unsigned long CThreadPoker::s_nRefCount = 0;
+
+
+//-----------------------------------------------------------------------------
+// CThreadPoker class.
+//-----------------------------------------------------------------------------
+PyObject *CThreadPoker::__enter__(PyObject *pSelf)
+{
+ ++s_nRefCount;
+
+ Py_INCREF(pSelf);
+ return pSelf;
+}
+
+void CThreadPoker::__exit__(PyObject *pSelf, PyObject *, PyObject *, PyObject *)
+{
+ if (!s_nRefCount) {
+ return;
+ }
+
+ --s_nRefCount;
+}
+
+void CThreadPoker::Poke()
+{
+ if (!s_nRefCount) {
+ return;
+ }
+
+ PyThreadState *pState = PyEval_SaveThread();
+ system(";");
+ PyEval_RestoreThread(pState);
+}
diff --git a/src/core/modules/listeners/listeners_tick.h b/src/core/modules/listeners/listeners_tick.h
new file mode 100644
index 000000000..6c704555a
--- /dev/null
+++ b/src/core/modules/listeners/listeners_tick.h
@@ -0,0 +1,52 @@
+/**
+* =============================================================================
+* Source Python
+* Copyright (C) 2012-2023 Source Python Development Team. All rights reserved.
+* =============================================================================
+*
+* This program is free software; you can redistribute it and/or modify it under
+* the terms of the GNU General Public License, version 3.0, as published by the
+* Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful, but WITHOUT
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+* details.
+*
+* You should have received a copy of the GNU General Public License along with
+* this program. If not, see .
+*
+* As a special exception, the Source Python Team gives you permission
+* to link the code of this program (as well as its derivative works) to
+* "Half-Life 2," the "Source Engine," and any Game MODs that run on software
+* by the Valve Corporation. You must obey the GNU General Public License in
+* all respects for all other code used. Additionally, the Source.Python
+* Development Team grants this exception to all derivative works.
+*/
+
+#ifndef _LISTENERS_TICK_H
+#define _LISTENERS_TICK_H
+
+//-----------------------------------------------------------------------------
+// Includes.
+//-----------------------------------------------------------------------------
+#include "boost/python.hpp"
+
+
+//-----------------------------------------------------------------------------
+// CThreadPoker class.
+//-----------------------------------------------------------------------------
+class CThreadPoker
+{
+private:
+ static unsigned long s_nRefCount;
+
+public:
+ static PyObject *__enter__(PyObject *pSelf);
+ static void __exit__(PyObject *pSelf, PyObject *, PyObject *, PyObject *);
+
+ static void Poke();
+};
+
+
+#endif // _LISTENERS_TICK_H
diff --git a/src/core/modules/listeners/listeners_tick_wrap.cpp b/src/core/modules/listeners/listeners_tick_wrap.cpp
new file mode 100644
index 000000000..6e2884750
--- /dev/null
+++ b/src/core/modules/listeners/listeners_tick_wrap.cpp
@@ -0,0 +1,72 @@
+/**
+* =============================================================================
+* Source Python
+* Copyright (C) 2012-2023 Source Python Development Team. All rights reserved.
+* =============================================================================
+*
+* This program is free software; you can redistribute it and/or modify it under
+* the terms of the GNU General Public License, version 3.0, as published by the
+* Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful, but WITHOUT
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+* details.
+*
+* You should have received a copy of the GNU General Public License along with
+* this program. If not, see .
+*
+* As a special exception, the Source Python Team gives you permission
+* to link the code of this program (as well as its derivative works) to
+* "Half-Life 2," the "Source Engine," and any Game MODs that run on software
+* by the Valve Corporation. You must obey the GNU General Public License in
+* all respects for all other code used. Additionally, the Source.Python
+* Development Team grants this exception to all derivative works.
+*/
+
+//-----------------------------------------------------------------------------
+// Includes.
+//-----------------------------------------------------------------------------
+#include "export_main.h"
+#include "listeners_tick.h"
+#include "modules/memory/memory_tools.h"
+
+
+//-----------------------------------------------------------------------------
+// Namespaces.
+//-----------------------------------------------------------------------------
+using namespace boost::python;
+
+
+//-----------------------------------------------------------------------------
+// Forward declarations.
+//-----------------------------------------------------------------------------
+static void export_thread_poker(scope);
+
+
+//-----------------------------------------------------------------------------
+// Declare the _listeners._tick module.
+//-----------------------------------------------------------------------------
+DECLARE_SP_SUBMODULE(_listeners, _tick)
+{
+ export_thread_poker(_tick);
+}
+
+
+//-----------------------------------------------------------------------------
+// Exports CThreadPoker.
+//-----------------------------------------------------------------------------
+void export_thread_poker(scope _tick)
+{
+ class_ ThreadPoker("ThreadPoker");
+
+ // Special methods...
+ ThreadPoker.def("__enter__", &CThreadPoker::__enter__);
+ ThreadPoker.def("__exit__", &CThreadPoker::__exit__);
+
+ // Methods...
+ ThreadPoker.def("poke", &CThreadPoker::Poke).staticmethod("poke");
+
+ // Add memory tools...
+ ThreadPoker ADD_MEM_TOOLS(CThreadPoker);
+}
diff --git a/src/core/sp_main.cpp b/src/core/sp_main.cpp
index 6f236449a..c6ff50cdb 100755
--- a/src/core/sp_main.cpp
+++ b/src/core/sp_main.cpp
@@ -58,6 +58,7 @@
#include "manager.h"
#include "modules/listeners/listeners_manager.h"
+#include "modules/listeners/listeners_tick.h"
#include "utilities/conversions.h"
#include "modules/entities/entities.h"
#include "modules/entities/entities_entity.h"
@@ -381,6 +382,9 @@ void CSourcePython::ServerActivate( edict_t *pEdictList, int edictCount, int cli
void CSourcePython::GameFrame( bool simulating )
{
CALL_LISTENERS(OnTick);
+
+ // Poke active game threads
+ CThreadPoker::Poke();
}
//-----------------------------------------------------------------------------