/*** 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 { Parse::ParsedObject arguments; AuSPtr callback; CommandDispatch(const Parse::ParsedObject &arguments, const AuSPtr &callback) : arguments(arguments), callback(callback) {} }; static AuHashMap gCommands; static AuList gLineCallbacks; static AuList gPendingCommands; static AuMutex gMutex; static AuMutex gPendingCommandsMutex; static AuOptionalEx 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(AuMove(callback)) {} }; enum class EDispatchType { eNow, eSys, eAsync }; static bool Dispatch(const AuROString &string, EDispatchType type, AuOptionalEx workerId) { Parse::ParseResult res; AuSPtr callback; // TODO: try catch? { AU_LOCK_GLOBAL_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()) { AuLogWarn("Command {} does not exist", tag); return false; } auto const &cmdEntry = cmdItr->second; offset = 0; Parse::ParseState consumable(AuIO::Character::ProviderFromStringShared(cmdParse, offset)); auto status = Parse::Parse(consumable, cmdEntry.commandStructure, res); if (!status) { AuLogWarn("Couldn't parse command {}", string); return false; } if (type == EDispatchType::eSys) { return AuTryInsert(gPendingCommands, CommandDispatch(res.result, cmdEntry.callback)); } else { callback = cmdEntry.callback; } } } if (type == EDispatchType::eNow) { callback->OnCommand(res.result); } else if (workerId) { Async::DispatchOn(workerId.value(), [=]() -> void { callback->OnCommand(res.result); }); } return true; } AUKN_SYM void RemoveCommand(const AuROString &tag) { AU_LOCK_GLOBAL_GUARD(gPendingCommandsMutex); AuTryRemove(gCommands, tag); } AUKN_SYM void AddCommand(const AuROString &tag, const Parse::ParseObject &commandStructure, const AuSPtr &callback) { AU_LOCK_GLOBAL_GUARD(gPendingCommandsMutex); SysAssert(callback); gCommands.insert(AuMakePair(AuString(tag), Command(AuString(tag), commandStructure, callback))); } AUKN_SYM bool DispatchCommand(const AuROString &string) { return Dispatch(string, EDispatchType::eSys, gCommandDispatcher); } AUKN_SYM bool DispatchCommandThisThread(const AuROString &string) { return Dispatch(string, EDispatchType::eNow, {}); } AUKN_SYM bool DispatchCommandToAsyncRunner(const AuROString &string, Async::WorkerPId_t id) { return Dispatch(string, EDispatchType::eAsync, id); } void UpdateDispatcher(AuOptionalEx target) { AU_LOCK_GLOBAL_GUARD(gMutex); AU_LOCK_GUARD(gPendingCommandsMutex); // process commands before async app termination if ((!target.has_value())) { auto commands = AuExchange(gPendingCommands, {}); for (const auto &command : commands) { command.callback->OnCommand(command.arguments); } } gCommandDispatcher = target; } static void DispatchCommandsFromThis(const AuList &commands) { for (const auto &command : commands) { command.callback->OnCommand(command.arguments); } } void RunCommandFunction(const AuFunction &func) { if (!gCommandDispatcher) { func(); } else { DispatchOn(gCommandDispatcher.value(), func)->BlockUntilComplete(); } } void PumpCommands() { AU_LOCK_GLOBAL_GUARD(gMutex); AuList commands; { AU_LOCK_GUARD(gPendingCommandsMutex); commands = AuExchange(gPendingCommands, {}); if (commands.empty()) { return; } } RunCommandFunction([&commands]() { DispatchCommandsFromThis(commands); }); } }