Rework: Modularize Mind

Now we have modularized the Mind class to contain all of its
ComponentThreads. This enables us to run multiple mind instances
within the same SMO process, at least in theory.

We probably won't actually do this, but we want to ensure that the
design is clean enough to enable it.
This commit is contained in:
2025-09-03 14:43:00 -04:00
parent eb069c4a96
commit 0dc8abaa28
6 changed files with 292 additions and 305 deletions
+20 -78
View File
@@ -11,9 +11,12 @@
#include <pthread.h>
#include <sched.h>
#include <unistd.h>
#include <memory>
namespace smo {
class Mind; // Forward declaration
class ComponentThread
: public std::enable_shared_from_this<ComponentThread>
{
@@ -29,8 +32,8 @@ public:
N_ITEMS
};
ComponentThread(ThreadId id)
: id(id), name(getThreadName(id)),
ComponentThread(ThreadId _id, Mind& parent)
: id(_id), name(getThreadName(_id)), parent(parent),
work(io_service), pause_work(pause_io_service),
pinnedCpuId(-1),
thread(
@@ -45,34 +48,8 @@ public:
void initializeTls(void);
static const std::shared_ptr<ComponentThread> getSelf(void);
static std::shared_ptr<ComponentThread> getComponentThread(
ThreadId id = N_ITEMS)
{
if (id < 0 || id > N_ITEMS)
{
throw std::runtime_error(std::string(__func__)
+ ": Invalid thread ID");
}
return componentThreads[id];
}
// Overload: search by name
static std::shared_ptr<ComponentThread> getComponentThread(
const std::string& name)
{
for (auto& thread : componentThreads) {
if (thread->name == name) { return thread; }
}
throw std::runtime_error(std::string(__func__)
+ ": Thread name not found in componentThreads map");
}
static boost::asio::io_service& getEventLoop(
ThreadId id = MRNTT)
{
return getComponentThread(id)->getIoService();
}
static std::shared_ptr<ComponentThread> getMrntt();
Mind& getParent() const { return parent; }
typedef void (mainFn)(ComponentThread &self);
static mainFn main, marionetteMain;
@@ -91,17 +68,9 @@ public:
*/
void joltThreadReq(std::function<void()> callback = nullptr);
// Convenience wrappers
static void startAllMindThreadsReq(std::function<void()> callback = nullptr);
static void pauseAllMindThreadsReq(std::function<void()> callback = nullptr);
static void resumeAllMindThreadsReq(std::function<void()> callback = nullptr);
static void exitAllMindThreadsReq(std::function<void()> callback = nullptr);
static void joltAllMindThreadsReq(std::function<void()> callback = nullptr);
// CPU management methods
static int getAvailableCpuCount();
void pinToCpu(int cpuId);
static void distributeAndPinThreadsAcrossCpus();
enum class ThreadOp
{
@@ -112,8 +81,6 @@ public:
JOLT,
N_ITEMS
};
static void execOpOnAllMindThreadsReq(
ThreadOp op, std::function<void()> callback = nullptr);
// Intentionally doesn't take a callback.
void exceptionInd(ComponentThread& thread);
@@ -123,41 +90,20 @@ public:
public:
ThreadId id;
std::string name;
Mind &parent;
boost::asio::io_service io_service;
boost::asio::io_service::work work;
boost::asio::io_service pause_io_service;
boost::asio::io_service::work pause_work;
std::atomic<bool> keepLooping;
int pinnedCpuId;
/**
* Indicates whether all mind threads have been JOLTed at least once.
*
* JOLTing serves two critical purposes:
*
* 1. **Global Constructor Sequencing**: Since pthreads begin executing while
* global constructors are still being executed, globally defined pthreads
* cannot depend on global objects having been constructed. JOLTing is done
* by the CRT's main thread within main(), which provides a sequencing
* guarantee that global constructors have been called.
*
* 2. **shared_from_this Safety**: shared_from_this() requires a prior
* shared_ptr handle to be established. The global list of
* shared_ptr<ComponentThread> guarantees that at least one shared_ptr to
* each ComponentThread has been initialized before JOLTing occurs.
*
* This atomic flag ensures that JOLTing happens exactly once and provides
* a synchronization point for the entire system initialization.
*/
static std::atomic<bool> threadsHaveBeenJolted;
/* Always ensure that this is last so that the thread is spawned after
* everything else is constructed.
*/
std::thread thread;
static std::array<std::shared_ptr<ComponentThread>, ComponentThread::N_ITEMS>
componentThreads;
static const std::string threadNames[ComponentThread::N_ITEMS];
static const std::string getThreadName(ThreadId id)
{
if (id < 0 || id >= ComponentThread::N_ITEMS)
@@ -165,6 +111,17 @@ public:
throw std::runtime_error(std::string(__func__)
+ ": Invalid thread ID");
}
// Use function-local static to ensure proper initialization order
static const std::string threadNames[N_ITEMS] = {
"mrntt",
"director",
"simulator",
"subconscious",
"body",
"world"
};
return threadNames[id];
}
};
@@ -172,21 +129,6 @@ public:
namespace mrntt {
extern std::shared_ptr<ComponentThread> mrntt;
}
namespace director {
extern std::shared_ptr<ComponentThread> director;
}
namespace simulator {
extern std::shared_ptr<ComponentThread> canvas;
}
namespace subconscious {
extern std::shared_ptr<ComponentThread> subconscious;
}
namespace body {
extern std::shared_ptr<ComponentThread> body;
}
namespace world {
extern std::shared_ptr<ComponentThread> world;
}
} // namespace smo
+51 -3
View File
@@ -4,21 +4,44 @@
#include <config.h>
#include <thread>
#include <functional>
#include <memory>
#include <unordered_map>
#include <string>
#include <director/director.h>
#include <simulator/simulator.h>
#include <componentThread.h>
namespace smo {
class Mind
class Mind : public std::enable_shared_from_this<Mind>
{
public:
Mind(void) {}
Mind(void);
~Mind(void) = default;
void initialize(void);
void execute(void);
void finalizeReq(std::function<void()> callback);
// ComponentThread access methods
std::shared_ptr<ComponentThread> getComponentThread(
ComponentThread::ThreadId id) const;
std::shared_ptr<ComponentThread> getComponentThread(
const std::string& name) const;
// Get all this Mind's component threads.
std::vector<std::shared_ptr<ComponentThread>> getMindThreads() const;
// Thread management methods (moved from ComponentThread)
void startAllMindThreadsReq(std::function<void()> callback = nullptr);
void pauseAllMindThreadsReq(std::function<void()> callback = nullptr);
void resumeAllMindThreadsReq(std::function<void()> callback = nullptr);
void exitAllMindThreadsReq(std::function<void()> callback = nullptr);
void joltAllMindThreadsReq(std::function<void()> callback = nullptr);
// CPU distribution method
void distributeAndPinThreadsAcrossCpus();
public:
std::thread directorThread;
std::thread simulatorThread;
@@ -26,9 +49,34 @@ public:
director::Director director;
simulator::Simulator canvas;
private:
/**
* Indicates whether all mind threads have been JOLTed at least once.
*
* JOLTing serves two critical purposes:
*
* 1. **Global Constructor Sequencing**: Since pthreads begin executing while
* global constructors are still being executed, globally defined pthreads
* cannot depend on global objects having been constructed. JOLTing is done
* by the CRT's main thread within main(), which provides a sequencing
* guarantee that global constructors have been called.
*
* 2. **shared_from_this Safety**: shared_from_this() requires a prior
* shared_ptr handle to be established. The global list of
* shared_ptr<ComponentThread> guarantees that at least one shared_ptr to
* each ComponentThread has been initialized before JOLTing occurs.
*
* This flag ensures that JOLTing happens exactly once and provides
* a synchronization point for the entire system initialization.
*/
bool threadsHaveBeenJolted = false;
// Collection of ComponentThread instances (excluding marionette)
std::vector<std::shared_ptr<ComponentThread>> componentThreads;
};
extern Mind mind;
// Global Mind instance will be defined in marionette.cpp
extern std::shared_ptr<Mind> globalMind;
} // namespace smo