/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: Commands.cpp Date: 2021-6-12 Author: Reece ***/ #include #include "Commands.hpp" namespace Aurora::Console::Commands { struct Command; struct CommandDispatch; static AuHashMap gCommands; static AuList gLineCallbacks; static AuList gPendingCommands; static auto gMutex = AuThreadPrimitives::MutexUnique(); static auto gPendingCommandsMutex = AuThreadPrimitives::MutexUnique(); static Async::WorkerPId_t gCommandDispatcher; struct Command { AuString tag; Parse::ParseObject commandStructure; AuSPtr callback; Command(AuString tag, Parse::ParseObject commandStructure, const AuSPtr &callback) : tag(tag), commandStructure(commandStructure), callback(callback) {} Command(AuString tag, Parse::ParseObject commandStructure, AuSPtr &&callback) : tag(tag), commandStructure(commandStructure), callback(std::move(callback)) {} }; struct CommandDispatch { Parse::ParsedObject arguments; AuSPtr callback; CommandDispatch(const Parse::ParsedObject &arguments, const AuSPtr &callback) : arguments(arguments), callback(callback) {} }; enum class EDispatchType { eNow, eSys, eAsync }; static bool Dispatch(const AuString &string, EDispatchType type, Async::WorkerPId_t workerId) { Parse::ParseResult res; AuSPtr callback; { AU_LOCK_GUARD(gPendingCommandsMutex); { AuString tag; AuString cmdParse; AuMach offset; auto commandSplit = string.find(" "); if (commandSplit != AuString::npos) { tag = string.substr(0, commandSplit); cmdParse = string.substr(commandSplit + 1); } else { tag = string; } auto cmdItr = gCommands.find(tag); if (cmdItr == gCommands.end()) { LogWarn("Command {} does not exist", tag); return false; } auto const &cmdEntry = cmdItr->second; offset = 0; Parse::ParseState consumable(Parse::StringToConsumable(cmdParse, offset)); auto status = Parse::Parse(consumable, cmdEntry.commandStructure, res); if (!status) { LogWarn("Couldn't parse command {}", string); return false; } if (type == EDispatchType::eSys) { gPendingCommands.push_back(CommandDispatch(res.result, cmdEntry.callback)); } else { callback = cmdEntry.callback; } } } if (type == EDispatchType::eNow) { callback->OnCommand(res.result); } else { Async::DispatchWork(workerId, Async::TaskFromConsumerRefT([](const CommandDispatch &dispatch) -> void { dispatch.callback->OnCommand(dispatch.arguments); }), {}, CommandDispatch(res.result, callback), false ); } return true; } AUKN_SYM void AddCommand(const AuString &tag, const Parse::ParseObject &commandStructure, const AuSPtr &callback) { AU_LOCK_GUARD(gPendingCommandsMutex); gCommands.insert(AuMakePair(tag, Command(tag, commandStructure, callback))); } AUKN_SYM bool DispatchCommand(const AuString &string) { return Dispatch(string, EDispatchType::eSys, {}); } AUKN_SYM bool DispatchCommandThisThread(const AuString &string) { return Dispatch(string, EDispatchType::eNow, {}); } AUKN_SYM bool DispatchCommandToAsyncRunner(const AuString &string, Async::WorkerId_t id) { return Dispatch(string, EDispatchType::eAsync, AuAsync::WorkerPId_t(AuUnsafeRaiiToShared(AuAsync::GetAsyncApp()), id)); } AUKN_SYM bool DispatchCommandToAsyncRunner(const AuString &string, Async::WorkerPId_t id) { return Dispatch(string, EDispatchType::eAsync, id); } void UpdateDispatcher(AuOptional target) { AU_LOCK_GUARD(gMutex); AU_LOCK_GUARD(gPendingCommandsMutex); // process commands before async app termination if ((!target.has_value())) { auto commands = std::exchange(gPendingCommands, {}); for (const auto &command : commands) { command.callback->OnCommand(command.arguments); } } gCommandDispatcher = Async::WorkerPId_t(AuUnsafeRaiiToShared(AuAsync::GetAsyncApp()), target.value()); } static void DispatchCommandsFromThis(const AuList &commands) { for (const auto &command : commands) { command.callback->OnCommand(command.arguments); } } void PumpCommands() { gMutex->Lock(); gPendingCommandsMutex->Lock(); auto commands = std::exchange(gPendingCommands, {}); gPendingCommandsMutex->Unlock(); if ((gCommandDispatcher.pool == nullptr) || ((gCommandDispatcher.pool.get() == Async::GetAsyncApp()) && (gCommandDispatcher == Async::WorkerId_t{}))) { DispatchCommandsFromThis(commands); } else { NewWorkItem(gCommandDispatcher, AuMakeShared([&commands]() { DispatchCommandsFromThis(commands); }), true)->Dispatch()->BlockUntilComplete(); } gMutex->Unlock(); } }