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:
+30
-208
@@ -11,74 +11,15 @@ namespace smo {
|
||||
|
||||
thread_local std::shared_ptr<ComponentThread> thisComponentThread;
|
||||
|
||||
const std::string ComponentThread::threadNames[N_ITEMS] =
|
||||
{
|
||||
"mrntt",
|
||||
"director",
|
||||
"simulator",
|
||||
"subconscious",
|
||||
"body",
|
||||
"world"
|
||||
};
|
||||
|
||||
namespace mrntt {
|
||||
std::shared_ptr<ComponentThread> mrntt =
|
||||
std::make_shared<ComponentThread>(ComponentThread::MRNTT);
|
||||
}
|
||||
namespace director {
|
||||
/* The director is the seat of volition in Salmanoff. It receives sensor
|
||||
* events from the body and world, and uses them to direct its implexors
|
||||
* to implex new menties. It then loads the menties into canvas for simulation
|
||||
* and correlation with intrins, in order to form new attrimotions and
|
||||
* menties.
|
||||
*/
|
||||
std::shared_ptr<ComponentThread> director =
|
||||
std::make_shared<ComponentThread>(ComponentThread::DIRECTOR);
|
||||
}
|
||||
namespace simulator {
|
||||
/* The canvas is the simulation engine in Salmanoff. It receives menties and
|
||||
* simulates them in accordance with the instructions from director. It then
|
||||
* re-renders them into perception for director to get feedback.
|
||||
*/
|
||||
std::shared_ptr<ComponentThread> canvas =
|
||||
std::make_shared<ComponentThread>(ComponentThread::SIMULATOR);
|
||||
}
|
||||
namespace subconscious {
|
||||
/* The subconscious is the seat of memory in Salmanoff. It receives menties
|
||||
* from director and stores them in memory for later recall.
|
||||
*/
|
||||
std::shared_ptr<ComponentThread> subconscious =
|
||||
std::make_shared<ComponentThread>(ComponentThread::SUBCONSCIOUS);
|
||||
}
|
||||
namespace body {
|
||||
/* The body is a thread that polls, processes, and sends interoceptive sensor
|
||||
* events to director. It enables these events to occur asynchronously,
|
||||
* indepdendent any actions that the other threads are taking.
|
||||
*/
|
||||
std::shared_ptr<ComponentThread> body =
|
||||
std::make_shared<ComponentThread>(ComponentThread::BODY);
|
||||
}
|
||||
namespace world {
|
||||
/* The world performs the same functions as the body, but for extrospective
|
||||
* sensor events.
|
||||
*/
|
||||
std::shared_ptr<ComponentThread> world =
|
||||
std::make_shared<ComponentThread>(ComponentThread::WORLD);
|
||||
extern std::shared_ptr<ComponentThread> mrntt;
|
||||
}
|
||||
|
||||
// Initialize static state
|
||||
std::atomic<bool> ComponentThread::threadsHaveBeenJolted{false};
|
||||
|
||||
std::array<std::shared_ptr<ComponentThread>, ComponentThread::N_ITEMS>
|
||||
ComponentThread::componentThreads =
|
||||
// Implementation of static method
|
||||
std::shared_ptr<ComponentThread> ComponentThread::getMrntt()
|
||||
{
|
||||
mrntt::mrntt,
|
||||
director::director,
|
||||
simulator::canvas,
|
||||
subconscious::subconscious,
|
||||
body::body,
|
||||
world::world
|
||||
};
|
||||
return mrntt::mrntt;
|
||||
}
|
||||
|
||||
void ComponentThread::initializeTls(void)
|
||||
{
|
||||
@@ -239,125 +180,6 @@ void ComponentThread::joltThreadReq(std::function<void()> callback)
|
||||
});
|
||||
}
|
||||
|
||||
struct AllMindThreadsOpReqContext {
|
||||
AllMindThreadsOpReqContext() : nThreadsProcessed(0) {}
|
||||
|
||||
int nThreadsProcessed;
|
||||
};
|
||||
|
||||
static const std::string getOpName(ComponentThread::ThreadOp op)
|
||||
{
|
||||
if (op < (ComponentThread::ThreadOp)0
|
||||
|| op > ComponentThread::ThreadOp::JOLT)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": Invalid operation");
|
||||
}
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case ComponentThread::ThreadOp::START: return "starting";
|
||||
case ComponentThread::ThreadOp::PAUSE: return "pausing";
|
||||
case ComponentThread::ThreadOp::RESUME: return "resuming";
|
||||
case ComponentThread::ThreadOp::EXIT: return "exiting";
|
||||
case ComponentThread::ThreadOp::JOLT: return "jolting";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentThread::execOpOnAllMindThreadsReq(
|
||||
ThreadOp op, std::function<void()> callback
|
||||
)
|
||||
{
|
||||
std::shared_ptr<ComponentThread> self = getSelf();
|
||||
// Check that we're being called from the marionette thread
|
||||
if (self->id != MRNTT)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": invoked on non-mrntt thread " + self->name);
|
||||
}
|
||||
|
||||
std::cout << "Mrntt: " << getOpName(op) << " all mind threads." << "\n";
|
||||
|
||||
auto context = std::make_shared<AllMindThreadsOpReqContext>();
|
||||
const int N_THREADS_EXCEPT_MRNTT = ComponentThread::N_ITEMS - 1;
|
||||
|
||||
for (auto &currThread : ComponentThread::componentThreads)
|
||||
{
|
||||
if (currThread->id == ComponentThread::MRNTT)
|
||||
{ continue; }
|
||||
|
||||
auto threadCallback = [context, callback, N_THREADS_EXCEPT_MRNTT, op]()
|
||||
{
|
||||
++context->nThreadsProcessed;
|
||||
if (context->nThreadsProcessed < N_THREADS_EXCEPT_MRNTT)
|
||||
{ return; }
|
||||
|
||||
if (op == ThreadOp::EXIT)
|
||||
{
|
||||
// Special cleanup for exit operations
|
||||
for (auto &currThreadJ : ComponentThread::componentThreads)
|
||||
{
|
||||
if (currThreadJ->id == ComponentThread::MRNTT)
|
||||
{ continue; }
|
||||
|
||||
currThreadJ->thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Mrntt: all mind threads done " << getOpName(op) << "."
|
||||
<< "\n";
|
||||
|
||||
if (callback) { callback(); }
|
||||
};
|
||||
|
||||
switch (op) {
|
||||
case ThreadOp::START:
|
||||
currThread->startThreadReq(threadCallback);
|
||||
break;
|
||||
case ThreadOp::PAUSE:
|
||||
currThread->pauseThreadReq(threadCallback);
|
||||
break;
|
||||
case ThreadOp::RESUME:
|
||||
currThread->resumeThreadReq(threadCallback);
|
||||
break;
|
||||
case ThreadOp::EXIT:
|
||||
currThread->exitThreadReq(threadCallback);
|
||||
break;
|
||||
case ThreadOp::JOLT:
|
||||
currThread->joltThreadReq(threadCallback);
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("Invalid operation");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentThread::startAllMindThreadsReq(std::function<void()> callback)
|
||||
{
|
||||
execOpOnAllMindThreadsReq(ThreadOp::START, callback);
|
||||
}
|
||||
|
||||
void ComponentThread::pauseAllMindThreadsReq(std::function<void()> callback)
|
||||
{
|
||||
execOpOnAllMindThreadsReq(ThreadOp::PAUSE, callback);
|
||||
}
|
||||
|
||||
void ComponentThread::resumeAllMindThreadsReq(std::function<void()> callback)
|
||||
{
|
||||
execOpOnAllMindThreadsReq(ThreadOp::RESUME, callback);
|
||||
}
|
||||
|
||||
void ComponentThread::exitAllMindThreadsReq(std::function<void()> callback)
|
||||
{
|
||||
execOpOnAllMindThreadsReq(ThreadOp::EXIT, callback);
|
||||
}
|
||||
|
||||
void ComponentThread::joltAllMindThreadsReq(std::function<void()> callback)
|
||||
{
|
||||
execOpOnAllMindThreadsReq(ThreadOp::JOLT, callback);
|
||||
}
|
||||
|
||||
/* This shouldn't take a callback because the caller shouldn't expect to
|
||||
* Mrntt to send a reply signal to it. Sending this Indication means that
|
||||
* Mrntt will send the calling thread an exitThreadReq. When the caller
|
||||
@@ -382,8 +204,18 @@ void ComponentThread::exceptionInd(ComponentThread& thread)
|
||||
std::cerr << "Mrntt: Exception occurred: in thread "
|
||||
<< thread.name << ". Killing Salmanoff." << "\n";
|
||||
|
||||
// Delegate to common shutdown request
|
||||
mind.finalizeReq(smo::mrntt::exitMarionetteLoop);
|
||||
/** EXPLANATION:
|
||||
* An exception has occurred in one of a mind's threads. We need to
|
||||
* shut down all of that particular mind's threads.
|
||||
*/
|
||||
thread.parent.finalizeReq([]() {
|
||||
/** FIXME:
|
||||
* When we eventually support multiple minds, we should remove this
|
||||
* since it causes marionette to exit, even if there are other minds
|
||||
* that are still running.
|
||||
*/
|
||||
smo::mrntt::exitMarionetteLoop();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -397,13 +229,23 @@ void ComponentThread::userShutdownInd()
|
||||
|
||||
// Post the user shutdown to the mrntt thread.
|
||||
this->getIoService().post(
|
||||
[]()
|
||||
[this]()
|
||||
{
|
||||
std::cerr << "Mrntt: User requested shutdown (SIGINT)."
|
||||
<< " Killing Salmanoff." << "\n";
|
||||
|
||||
// Delegate to common shutdown request
|
||||
mind.finalizeReq(smo::mrntt::exitMarionetteLoop);
|
||||
/** EXPLANATION:
|
||||
* A user has requested a shutdown. We need to shut down all of the
|
||||
* threads in all running Minds.
|
||||
*/
|
||||
parent.finalizeReq([]() {
|
||||
/** FIXME:
|
||||
* When we eventually support multiple minds, we should remove this
|
||||
* since it causes marionette to exit, even if there are other minds
|
||||
* that are still running.
|
||||
*/
|
||||
smo::mrntt::exitMarionetteLoop();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -455,24 +297,4 @@ void ComponentThread::pinToCpu(int cpuId)
|
||||
std::cout << name << ": Pinned to CPU " << cpuId << "\n";
|
||||
}
|
||||
|
||||
void ComponentThread::distributeAndPinThreadsAcrossCpus()
|
||||
{
|
||||
int cpuCount = getAvailableCpuCount();
|
||||
std::cout << "Available CPUs: " << cpuCount << "\n";
|
||||
|
||||
// Skip the marionette thread (MRNTT) as it's the control thread
|
||||
int threadIndex = 0;
|
||||
for (auto& thread : componentThreads)
|
||||
{
|
||||
if (thread->id == MRNTT) { continue; }
|
||||
|
||||
int targetCpu = threadIndex % cpuCount;
|
||||
thread->pinToCpu(targetCpu);
|
||||
++threadIndex;
|
||||
}
|
||||
|
||||
std::cout << "Distributed " << (threadIndex) << " threads across "
|
||||
<< cpuCount << " CPUs\n";
|
||||
}
|
||||
|
||||
} // namespace smo
|
||||
|
||||
Reference in New Issue
Block a user