mirror of
https://github.com/VCMP-SqMod/SqMod.git
synced 2024-11-08 08:47:17 +01:00
4a6bfc086c
Switched to POCO library for unified platform/library interface. Deprecated the external module API. It was creating more problems than solving. Removed most built-in libraries in favor of system libraries for easier maintenance. Cleaned and secured code with help from static analyzers.
1960 lines
69 KiB
C++
1960 lines
69 KiB
C++
#pragma once
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
#include "Core/Buffer.hpp"
|
|
#include "Core/Utility.hpp"
|
|
#include "Logger.hpp"
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
#include <cctype>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <map>
|
|
#include <vector>
|
|
#include <iterator>
|
|
#include <algorithm>
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
namespace SqMod { // NOLINT(modernize-concat-nested-namespaces)
|
|
namespace Cmd {
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Forward declarations.
|
|
*/
|
|
class Context;
|
|
class Guard;
|
|
class Command;
|
|
class Controller;
|
|
class Manager;
|
|
class Listener;
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Helper typedefs.
|
|
*/
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
typedef SharedPtr< Context > CtxRef; // Shared reference to an execution context.
|
|
typedef WeakPtr< Context > CtxPtr; // Shared reference to an execution context.
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
typedef SharedPtr< Controller > CtrRef; // Shared reference to a command controller.
|
|
typedef WeakPtr< Controller > CtrPtr; // Shared reference to a command controller.
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
typedef std::pair< uint8_t, Object > Argument; // Can hold the argument value and type.
|
|
typedef std::vector< Argument > Arguments; // A list of extracted arguments.
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
typedef std::vector< Command > Commands; // List of attached command instances.
|
|
typedef std::vector< Controller * > Controllers; // List of active controllers.
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Types of arguments supported by the command system.
|
|
*/
|
|
enum CmdArgType
|
|
{
|
|
CMDARG_ANY = 0u,
|
|
CMDARG_INTEGER = (1u << 1u),
|
|
CMDARG_FLOAT = (1u << 2u),
|
|
CMDARG_BOOLEAN = (1u << 3u),
|
|
CMDARG_STRING = (1u << 4u),
|
|
CMDARG_LOWER = (1u << 5u),
|
|
CMDARG_UPPER = (1u << 6u),
|
|
CMDARG_GREEDY = (1u << 7u)
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Types of errors reported by the command system.
|
|
*/
|
|
enum CmdError
|
|
{
|
|
// The command failed for unknown reasons
|
|
CMDERR_UNKNOWN = 0u,
|
|
// The command failed to execute because there was nothing to execute
|
|
CMDERR_EMPTY_COMMAND,
|
|
// The command failed to execute because the command name was invalid after processing
|
|
CMDERR_INVALID_COMMAND,
|
|
// The command failed to execute because there was a syntax error in the arguments
|
|
CMDERR_SYNTAX_ERROR,
|
|
// The command failed to execute because there was no such command
|
|
CMDERR_UNKNOWN_COMMAND,
|
|
// The command failed to execute because the it's currently suspended
|
|
CMDERR_COMMAND_SUSPENDED,
|
|
// The command failed to execute because the invoker does not have the proper authority
|
|
CMDERR_INSUFFICIENT_AUTH,
|
|
// The command failed to execute because there was no callback to handle the execution
|
|
CMDERR_MISSING_EXECUTER,
|
|
// The command was unable to execute because the argument limit was not reached
|
|
CMDERR_INCOMPLETE_ARGS,
|
|
// The command was unable to execute because the argument limit was exceeded
|
|
CMDERR_EXTRANEOUS_ARGS,
|
|
// Command was unable to execute due to argument type mismatch
|
|
CMDERR_UNSUPPORTED_ARG,
|
|
// The command arguments contained more data than the internal buffer can handle
|
|
CMDERR_BUFFER_OVERFLOW,
|
|
// The command failed to complete execution due to a runtime exception
|
|
CMDERR_EXECUTION_FAILED,
|
|
// The command completed the execution but returned a negative result
|
|
CMDERR_EXECUTION_ABORTED,
|
|
// The post execution callback failed to execute due to a runtime exception
|
|
CMDERR_POST_PROCESSING_FAILED,
|
|
// The callback that was supposed to deal with the failure also failed due to a runtime exception
|
|
CMDERR_UNRESOLVED_FAILURE,
|
|
// Maximum command error identifier
|
|
CMDERR_MAX
|
|
};
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
inline const SQChar * ValidateName(const SQChar * name)
|
|
{
|
|
// Is the name empty?
|
|
if (!name || *name == '\0')
|
|
{
|
|
STHROWF("Invalid or empty command name");
|
|
}
|
|
// Create iterator to name start
|
|
const SQChar * str = name;
|
|
// Inspect name characters
|
|
while ('\0' != *str)
|
|
{
|
|
// Does it contain spaces?
|
|
if (std::isspace(*str) != 0)
|
|
{
|
|
STHROWF("Command names cannot contain spaces");
|
|
}
|
|
// Move to the next character
|
|
++str;
|
|
}
|
|
// Return the name
|
|
return name;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
inline const SQChar * ArgSpecToStr(uint8_t spec)
|
|
{
|
|
switch (spec)
|
|
{
|
|
case CMDARG_ANY: return _SC("any");
|
|
case CMDARG_INTEGER: return _SC("integer");
|
|
case CMDARG_FLOAT: return _SC("float");
|
|
case CMDARG_BOOLEAN: return _SC("boolean");
|
|
case CMDARG_STRING:
|
|
case CMDARG_LOWER:
|
|
case CMDARG_UPPER:
|
|
case CMDARG_GREEDY: return _SC("string");
|
|
default: return _SC("unknown");
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Holds the context of a command execution.
|
|
*/
|
|
struct Context
|
|
{
|
|
public:
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
Buffer mBuffer; // Shared buffer used to extract arguments and process data.
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
const Object mInvoker; // Reference to the entity that invoked the command.
|
|
String mCommand; // Command name extracted from the command string.
|
|
String mArgument; // Command argument extracted from the command string.
|
|
Listener* mInstance; // Pointer to the currently executed command listener.
|
|
Object mObject; // Script object of the currently executed command.
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
Arguments mArgv; // Extracted command arguments.
|
|
uint32_t mArgc; // Extracted arguments count.
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Default constructor.
|
|
*/
|
|
explicit Context(Object & invoker)
|
|
: mBuffer(512)
|
|
, mInvoker(invoker)
|
|
, mCommand()
|
|
, mArgument()
|
|
, mInstance(nullptr)
|
|
, mObject()
|
|
, mArgv()
|
|
, mArgc(0)
|
|
{
|
|
// Reserve enough space upfront
|
|
mCommand.reserve(64);
|
|
mArgument.reserve(512);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Copy constructor. (disabled)
|
|
*/
|
|
Context(const Context & o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Move constructor. (disabled)
|
|
*/
|
|
Context(Context && o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Copy assignment operator. (disabled)
|
|
*/
|
|
Context & operator = (const Context & o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Move assignment operator. (disabled)
|
|
*/
|
|
Context & operator = (Context && o) = delete;
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Helper class implementing RAII to release the command context.
|
|
*/
|
|
struct Guard
|
|
{
|
|
public:
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
CtrRef mController; // Reference to the guarded controller.
|
|
CtxRef mPrevious; // Previous context when this guard was created.
|
|
CtxRef mCurrent; // The context managed by this guard.
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Default constructor.
|
|
*/
|
|
Guard(CtrRef ctr, Object & invoker);
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Copy constructor.
|
|
*/
|
|
Guard(const Guard & o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Move constructor.
|
|
*/
|
|
Guard(Guard && o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Destructor.
|
|
*/
|
|
~Guard();
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Copy assignment operator.
|
|
*/
|
|
Guard & operator = (const Guard & o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Move assignment operator.
|
|
*/
|
|
Guard & operator = (Guard && o) = delete;
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Structure that represents a unique command in the pool.
|
|
*/
|
|
struct Command
|
|
{
|
|
// --------------------------------------------------------------------------------------------
|
|
std::size_t mHash; // The unique hash that identifies this command.
|
|
String mName; // The unique name that identifies this command.
|
|
Listener* mPtr; // The listener that reacts to this command.
|
|
Object mObj; // A strong reference to the script object.
|
|
CtrPtr mCtr; // The associated controller.
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Construct a command and the also create a script object from the specified listener.
|
|
*/
|
|
Command(std::size_t hash, String name, Listener * ptr, CtrPtr ctr);
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Construct a command and extract the listener from the specified script object.
|
|
*/
|
|
Command(std::size_t hash, String name, const Object & obj, CtrPtr ctr);
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Construct a command and extract the listener from the specified script object.
|
|
*/
|
|
Command(std::size_t hash, String name, Object && obj, CtrPtr ctr);
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Construct a command with the given parameters.
|
|
*/
|
|
Command(std::size_t hash, String name, Listener * ptr, const Object & obj, CtrPtr ctr);
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Construct a command with the given parameters.
|
|
*/
|
|
Command(std::size_t hash, String name, Listener * ptr, Object && obj, CtrPtr ctr);
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Copy constructor.
|
|
*/
|
|
Command(const Command & o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Move constructor.
|
|
*/
|
|
Command(Command && o) noexcept
|
|
: mHash(o.mHash)
|
|
, mName(std::forward< String >(o.mName))
|
|
, mPtr(o.mPtr)
|
|
, mObj(std::forward< Object >(o.mObj))
|
|
, mCtr(std::forward< CtrPtr >(o.mCtr))
|
|
{
|
|
o.mPtr = nullptr;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Destructor.
|
|
*/
|
|
~Command();
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Copy assignment operator.
|
|
*/
|
|
Command & operator = (const Command & o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Move assignment operator.
|
|
*/
|
|
Command & operator = (Command && o) noexcept
|
|
{
|
|
if (this != &o)
|
|
{
|
|
mHash = o.mHash;
|
|
mName = std::forward< String >(o.mName);
|
|
mPtr = o.mPtr;
|
|
mObj = std::forward< Object >(o.mObj);
|
|
mCtr = o.mCtr;
|
|
o.mPtr = nullptr;
|
|
}
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Holds a list of commands to execute as well as authentication or failure resolvers.
|
|
*/
|
|
struct Controller
|
|
{
|
|
// --------------------------------------------------------------------------------------------
|
|
friend class Guard; // Allow the guard to swap the execution context.
|
|
friend class Manager; // Allow the manager to manage the controller.
|
|
friend class Listener; // Allow the listener to attach and detach.
|
|
|
|
private:
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
Commands m_Commands; // List of available command instances.
|
|
CtxRef m_Context; // Context of the currently executed command.
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
Function m_OnFail; // Callback when something failed while running a command.
|
|
Function m_OnAuth; // Callback to authenticate execution for a certain invoker.
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
Manager * m_Manager;
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
static Controllers s_Controllers;
|
|
|
|
protected:
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Default constructor.
|
|
*/
|
|
explicit Controller(Manager * mgr)
|
|
: m_Commands()
|
|
, m_Context()
|
|
, m_OnFail()
|
|
, m_OnAuth()
|
|
, m_Manager(mgr)
|
|
{
|
|
s_Controllers.push_back(this);
|
|
}
|
|
|
|
protected:
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Forward error message to the error callback.
|
|
*/
|
|
template < typename T > void SqError(int32_t type, const SQChar * msg, T data)
|
|
{
|
|
// Is there a callback that deals with errors?
|
|
if (m_OnFail.IsNull())
|
|
{
|
|
return;
|
|
}
|
|
// Attempt to forward the error to that callback
|
|
try
|
|
{
|
|
m_OnFail.Execute(type, msg, data);
|
|
}
|
|
catch (const std::exception & e)
|
|
{
|
|
// The debugger probably took care of reporting the details
|
|
LogErr("Command error callback failed [%s]", e.what());
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Execute one of the managed commands.
|
|
*/
|
|
int32_t Run(const Guard & guard, const SQChar * command);
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Attempt to execute the specified command.
|
|
*/
|
|
int32_t Exec(Context & ctx);
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Attempt to parse the specified argument.
|
|
*/
|
|
bool Parse(Context & ctx);
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Attach a command listener to a certain name.
|
|
*/
|
|
Object & Attach(Object & obj, Listener * ptr)
|
|
{
|
|
return Attach(Object(obj), ptr);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Attach a command listener to a certain name.
|
|
*/
|
|
Object & Attach(const Object & obj, Listener * ptr)
|
|
{
|
|
return Attach(Object(obj), ptr);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Attach a command listener to a certain name.
|
|
*/
|
|
Object & Attach(Object && obj, Listener * ptr);
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Detach a command listener from a certain name.
|
|
*/
|
|
void Detach(const String & name)
|
|
{
|
|
// Obtain the unique identifier of the specified name
|
|
const std::size_t hash = std::hash< String >()(name);
|
|
// Iterator to the found command, if any
|
|
auto itr = m_Commands.cbegin();
|
|
// Attempt to find the specified command
|
|
for (; itr != m_Commands.cend(); ++itr)
|
|
{
|
|
// Are the hashes identical?
|
|
if (itr->mHash == hash)
|
|
{
|
|
break; // We found our command!
|
|
}
|
|
}
|
|
// Make sure the command exist before attempting to remove it
|
|
if (itr != m_Commands.end())
|
|
{
|
|
m_Commands.erase(itr);
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Detach a command listener from a certain name.
|
|
*/
|
|
void Detach(Listener * ptr)
|
|
{
|
|
// Iterator to the found command, if any
|
|
auto itr = m_Commands.cbegin();
|
|
// Attempt to find the specified command
|
|
for (; itr != m_Commands.cend(); ++itr)
|
|
{
|
|
// Are the instances identical?
|
|
if (itr->mPtr == ptr)
|
|
{
|
|
break; // We found our command!
|
|
}
|
|
}
|
|
// Make sure the command exists before attempting to remove it
|
|
if (itr != m_Commands.end())
|
|
{
|
|
m_Commands.erase(itr);
|
|
}
|
|
}
|
|
|
|
public:
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Copy constructor. (disabled)
|
|
*/
|
|
Controller(const Controller & o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Move constructor. (disabled)
|
|
*/
|
|
Controller(Controller && o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Copy assignment operator. (disabled)
|
|
*/
|
|
Controller & operator = (const Controller & o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Move assignment operator. (disabled)
|
|
*/
|
|
Controller & operator = (Controller && o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Terminate the all controllers by releasing their command listeners and callbacks.
|
|
*/
|
|
static void Terminate()
|
|
{
|
|
for (auto & ctr : s_Controllers)
|
|
{
|
|
// Clear the command listeners
|
|
ctr->Clear();
|
|
// Release the script callbacks, if any
|
|
ctr->m_OnFail.Release();
|
|
ctr->m_OnAuth.Release();
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Destructor.
|
|
*/
|
|
~Controller()
|
|
{
|
|
s_Controllers.erase(std::remove(s_Controllers.begin(), s_Controllers.end(), this),
|
|
s_Controllers.end());
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the currently active execution context.
|
|
*/
|
|
SQMOD_NODISCARD const CtxRef & GetCtx() const
|
|
{
|
|
return m_Context;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* See whether a certain name exist in the command list.
|
|
*/
|
|
SQMOD_NODISCARD bool Attached(const String & name) const
|
|
{
|
|
// Obtain the unique identifier of the specified name
|
|
const std::size_t hash = std::hash< String >()(name);
|
|
// Attempt to find the specified command
|
|
for (const auto & cmd : m_Commands) // NOLINT(readability-use-anyofallof)
|
|
{
|
|
// Are the hashes identical?
|
|
if (cmd.mHash == hash)
|
|
{
|
|
return true; // We found our command!
|
|
}
|
|
}
|
|
// No such command exists
|
|
return false;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* See whether a certain instance exist in the command list.
|
|
*/
|
|
bool Attached(const Listener * ptr) const
|
|
{
|
|
// Attempt to find the specified command
|
|
for (const auto & cmd : m_Commands) // NOLINT(readability-use-anyofallof)
|
|
{
|
|
// Are the instances identical?
|
|
if (cmd.mPtr == ptr)
|
|
{
|
|
return true; // We found our command!
|
|
}
|
|
}
|
|
// No such command exists
|
|
return false;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Sort the command list in an ascending order.
|
|
*/
|
|
void Sort()
|
|
{
|
|
std::sort(m_Commands.begin(), m_Commands.end(),
|
|
[](Commands::const_reference a, Commands::const_reference b) -> bool {
|
|
return (a.mName < b.mName); // NOLINT(modernize-use-nullptr)
|
|
});
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Detach all the associated command listeners.
|
|
*/
|
|
void Clear()
|
|
{
|
|
m_Commands.clear();
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Locate and retrieve a command listener by name.
|
|
*/
|
|
const Object & FindByName(const String & name)
|
|
{
|
|
// Obtain the unique identifier of the specified name
|
|
const std::size_t hash = std::hash< String >()(name);
|
|
// Attempt to find the specified command
|
|
for (const auto & cmd : m_Commands)
|
|
{
|
|
// Are the hashes identical?
|
|
if (cmd.mHash == hash)
|
|
{
|
|
return cmd.mObj; // We found our command!
|
|
}
|
|
}
|
|
// No such command exist
|
|
return NullObject();
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the error callback.
|
|
*/
|
|
Function & GetOnFail()
|
|
{
|
|
return m_OnFail;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Modify the error callback.
|
|
*/
|
|
void SetOnFail(Function & func)
|
|
{
|
|
m_OnFail = std::move(func);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the authentication callback.
|
|
*/
|
|
Function & GetOnAuth()
|
|
{
|
|
return m_OnAuth;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Modify the authentication callback.
|
|
*/
|
|
void SetOnAuth(Function & func)
|
|
{
|
|
m_OnAuth = std::move(func);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* See whether an execution context is currently active.
|
|
*/
|
|
SQMOD_NODISCARD bool IsContext() const
|
|
{
|
|
return !!m_Context;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the invoker from the current execution context.
|
|
*/
|
|
SQMOD_NODISCARD const Object & GetInvoker() const
|
|
{
|
|
// See if there's an execution context available
|
|
if (!m_Context)
|
|
{
|
|
STHROWF("No active execution context");
|
|
}
|
|
// Return the requested information
|
|
return m_Context->mInvoker;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the listener object from the current execution context.
|
|
*/
|
|
SQMOD_NODISCARD const Object & GetListener() const
|
|
{
|
|
// See if there's an execution context available
|
|
if (!m_Context)
|
|
{
|
|
STHROWF("No active execution context");
|
|
}
|
|
// Return the requested information
|
|
return m_Context->mObject;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the command name from the current execution context.
|
|
*/
|
|
SQMOD_NODISCARD const String & GetCommand() const
|
|
{
|
|
// See if there's an execution context available
|
|
if (!m_Context)
|
|
{
|
|
STHROWF("No active execution context");
|
|
}
|
|
// Return the requested information
|
|
return m_Context->mCommand;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the command argument from the current execution context.
|
|
*/
|
|
SQMOD_NODISCARD const String & GetArgument() const
|
|
{
|
|
// See if there's an execution context available
|
|
if (!m_Context)
|
|
{
|
|
STHROWF("No active execution context");
|
|
}
|
|
// Return the requested information
|
|
return m_Context->mArgument;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve all command listeners in an array.
|
|
*/
|
|
SQMOD_NODISCARD Array GetCommandsArray() const
|
|
{
|
|
// Allocate an array with an adequate size
|
|
Array arr(SqVM(), m_Commands.size());
|
|
// Index of the currently processed command listener
|
|
SQInteger index = 0;
|
|
// Populate the array with the command listeners
|
|
for (const auto & cmd : m_Commands)
|
|
{
|
|
arr.SetValue(index++, cmd.mObj);
|
|
}
|
|
// Return the resulted array
|
|
return arr;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve all command listeners in a table.
|
|
*/
|
|
SQMOD_NODISCARD Table GetCommandsTable() const
|
|
{
|
|
// Allocate an empty table
|
|
Table tbl(SqVM());
|
|
// Populate the table with the command listeners
|
|
for (const auto & cmd : m_Commands)
|
|
{
|
|
tbl.SetValue(cmd.mName.c_str(), cmd.mObj);
|
|
}
|
|
// Return the resulted table
|
|
return tbl;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Process all command listeners with a function.
|
|
*/
|
|
void ForeachCommand(Function & func) const
|
|
{
|
|
// Make sure that the specified function works
|
|
if (func.IsNull())
|
|
{
|
|
return;
|
|
}
|
|
// Process all the managed command listeners
|
|
for (const auto & cmd : m_Commands)
|
|
{
|
|
func.Execute(cmd.mObj);
|
|
}
|
|
}
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Allows interaction with a command controller from script.
|
|
*/
|
|
class Manager
|
|
{
|
|
private:
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
CtrRef m_Controller; // Reference to the managed controller.
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the associated controller if valid otherwise throw an exception.
|
|
*/
|
|
SQMOD_NODISCARD const CtrRef & GetValid() const
|
|
{
|
|
// Validate the managed controller
|
|
if (!m_Controller)
|
|
{
|
|
STHROWF("No controller associated with this manager");
|
|
}
|
|
// Return the controller reference
|
|
return m_Controller;
|
|
}
|
|
|
|
public:
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Default constructor.
|
|
*/
|
|
Manager()
|
|
: m_Controller(new Controller(this))
|
|
{
|
|
/* ... */
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Copy constructor. (disabled)
|
|
*/
|
|
Manager(const Manager & o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Move constructor. (disabled)
|
|
*/
|
|
Manager(Manager && o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Copy assignment operator. (disabled)
|
|
*/
|
|
Manager & operator = (const Manager & o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Move assignment operator. (disabled)
|
|
*/
|
|
Manager & operator = (Manager && o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Used by the script engine to compare two instances of this type.
|
|
*/
|
|
SQMOD_NODISCARD int32_t Cmp(const Manager & o) const
|
|
{
|
|
if (m_Controller == o.m_Controller)
|
|
{
|
|
return 0;
|
|
}
|
|
else if (m_Controller.Get() > o.m_Controller.Get())
|
|
{
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Used by the script engine to convert this instance to a string.
|
|
*/
|
|
SQMOD_NODISCARD String ToString() const
|
|
{
|
|
return fmt::format("{}", m_Controller.Count());
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the associated controller reference.
|
|
*/
|
|
SQMOD_NODISCARD const CtrRef & GetCtr() const
|
|
{
|
|
return m_Controller;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the number of references to the managed controller.
|
|
*/
|
|
SQMOD_NODISCARD uint32_t GetRefCount() const
|
|
{
|
|
return m_Controller.Count();
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Run a command under a specific invoker.
|
|
*/
|
|
int32_t Run(Object & invoker, StackStrF & command)
|
|
{
|
|
if ((SQ_FAILED(command.Proc())))
|
|
{
|
|
return static_cast< int32_t >(command.mRes);
|
|
}
|
|
else
|
|
{
|
|
return GetValid()->Run(Guard(m_Controller, invoker), command.mPtr);
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Sort the command list in an ascending order.
|
|
*/
|
|
void Sort()
|
|
{
|
|
GetValid()->Sort();
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Detach all the associated command listeners.
|
|
*/
|
|
void Clear()
|
|
{
|
|
GetValid()->Clear();
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Attach a command listener to the managed controller.
|
|
*/
|
|
void Attach(Object & obj)
|
|
{
|
|
GetValid()->Attach(obj, nullptr);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Locate and retrieve a command listener by name.
|
|
*/
|
|
const Object & FindByName(StackStrF & name)
|
|
{
|
|
// Validate the specified name
|
|
if ((SQ_FAILED(name.Proc())))
|
|
{
|
|
STHROWF("Unable to extract a valid command name");
|
|
}
|
|
else if (name.mLen <= 0)
|
|
{
|
|
STHROWF("Invalid or empty command name");
|
|
}
|
|
// Attempt to return the requested command
|
|
return GetValid()->FindByName(String(name.mPtr, static_cast< size_t >(name.mLen)));
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the number of managed command listeners.
|
|
*/
|
|
SQMOD_NODISCARD SQInteger GetCount() const
|
|
{
|
|
return ConvTo< SQInteger >::From(GetValid()->m_Commands.size());
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* See whether an execution context is currently active.
|
|
*/
|
|
SQMOD_NODISCARD bool IsContext() const
|
|
{
|
|
return GetValid()->IsContext();
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the error callback.
|
|
*/
|
|
Function & GetOnFail()
|
|
{
|
|
return GetValid()->GetOnFail();
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Modify the error callback.
|
|
*/
|
|
void SetOnFail(Function & func)
|
|
{
|
|
GetValid()->SetOnFail(func);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the authentication callback.
|
|
*/
|
|
Function & GetOnAuth()
|
|
{
|
|
return GetValid()->GetOnAuth();
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Modify the authentication callback.
|
|
*/
|
|
void SetOnAuth(Function & func)
|
|
{
|
|
GetValid()->SetOnAuth(func);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the invoker from the current execution context.
|
|
*/
|
|
SQMOD_NODISCARD const Object & GetInvoker() const
|
|
{
|
|
return GetValid()->GetInvoker();
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the listener object from the current execution context.
|
|
*/
|
|
SQMOD_NODISCARD const Object & GetListener() const
|
|
{
|
|
return GetValid()->GetListener();
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the command name from the current execution context.
|
|
*/
|
|
SQMOD_NODISCARD const String & GetCommand() const
|
|
{
|
|
return GetValid()->GetCommand();
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the command argument from the current execution context.
|
|
*/
|
|
SQMOD_NODISCARD const String & GetArgument() const
|
|
{
|
|
return GetValid()->GetArgument();
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve all command listeners in an array.
|
|
*/
|
|
SQMOD_NODISCARD Array GetCommandsArray() const
|
|
{
|
|
return GetValid()->GetCommandsArray();
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve all command listeners in a table.
|
|
*/
|
|
SQMOD_NODISCARD Table GetCommandsTable() const
|
|
{
|
|
return GetValid()->GetCommandsTable();
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Process all command listeners with a function.
|
|
*/
|
|
void ForeachCommand(Function & func) const
|
|
{
|
|
GetValid()->ForeachCommand(func);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Create command instances and obtain the associated object.
|
|
*/
|
|
Object Create1(StackStrF & name)
|
|
{
|
|
return Create(name, StackStrF::Dummy(), NullArray(), 0, SQMOD_MAX_CMD_ARGS-1, -1, false, false);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Create command instances and obtain the associated object.
|
|
*/
|
|
Object Create2(StackStrF & name, StackStrF & spec)
|
|
{
|
|
return Create(name, spec, NullArray(), 0, SQMOD_MAX_CMD_ARGS-1, -1, false, false);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Create command instances and obtain the associated object.
|
|
*/
|
|
Object Create3(StackStrF & name, StackStrF & spec, Array & tags)
|
|
{
|
|
return Create(name, spec, tags, 0, SQMOD_MAX_CMD_ARGS-1, -1, false, false);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Create command instances and obtain the associated object.
|
|
*/
|
|
Object Create4(StackStrF & name, StackStrF & spec, uint8_t min, uint8_t max)
|
|
{
|
|
return Create(name, spec, NullArray(), min, max, -1, false, false);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Create command instances and obtain the associated object.
|
|
*/
|
|
Object Create5(StackStrF & name, StackStrF & spec, Array & tags, uint8_t min, uint8_t max)
|
|
{
|
|
return Create(name, spec, tags, min, max, -1, false, false);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Create command instances and obtain the associated object.
|
|
*/
|
|
Object Create6(StackStrF & name, StackStrF & spec, Array & tags, uint8_t min, uint8_t max, SQInteger auth)
|
|
{
|
|
return Create(name, spec, tags, min, max, auth, auth >= 0, false);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Create command instances and obtain the associated object.
|
|
*/
|
|
Object Create7(StackStrF & name, StackStrF & spec, Array & tags, uint8_t min, uint8_t max, SQInteger auth, bool prot)
|
|
{
|
|
return Create(name, spec, tags, min, max, auth, prot, false);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Create command instances and obtain the associated object.
|
|
*/
|
|
Object Create(StackStrF & name, StackStrF & spec, Array & tags, uint8_t min, uint8_t max, SQInteger auth, bool prot, bool assoc);
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Attaches to a command name and listens for invocations.
|
|
*/
|
|
class Listener
|
|
{
|
|
// --------------------------------------------------------------------------------------------
|
|
friend class Controller; // Allow the controller to execute this listener.
|
|
friend class Command; // Allow the command to interact with this listener.
|
|
friend class Manager; // Allow the manager to interact with this listener.
|
|
|
|
public:
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Release any trace of script resources from all the listener instances.
|
|
*/
|
|
static void Terminate()
|
|
{
|
|
// Go forward and release resources
|
|
for (Listener * node = s_Head; node != nullptr; node = node->m_Next)
|
|
{
|
|
node->m_Data.Release();
|
|
node->m_OnExec.Release();
|
|
node->m_OnAuth.Release();
|
|
node->m_OnPost.Release();
|
|
node->m_OnFail.Release();
|
|
}
|
|
// Go backwards and release resources
|
|
for (Listener * node = s_Head; node != nullptr; node = node->m_Prev)
|
|
{
|
|
node->m_Data.Release();
|
|
node->m_OnExec.Release();
|
|
node->m_OnAuth.Release();
|
|
node->m_OnPost.Release();
|
|
node->m_OnFail.Release();
|
|
}
|
|
// Kinda useless but Squirrel doesn't play nice with loose references
|
|
// Better safe than sorry
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Convenience constructor.
|
|
*/
|
|
explicit Listener(StackStrF & name)
|
|
: Listener(name, StackStrF::Dummy(), NullArray(), 0, SQMOD_MAX_CMD_ARGS-1, -1, false, false)
|
|
{
|
|
/* ... */
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Convenience constructor.
|
|
*/
|
|
Listener(StackStrF & name, StackStrF & spec)
|
|
: Listener(name, spec, NullArray(), 0, SQMOD_MAX_CMD_ARGS-1, -1, false, false)
|
|
{
|
|
/* ... */
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Convenience constructor.
|
|
*/
|
|
Listener(StackStrF & name, StackStrF & spec, Array & tags)
|
|
: Listener(name, spec, tags, 0, SQMOD_MAX_CMD_ARGS-1, -1, false, false)
|
|
{
|
|
/* ... */
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Convenience constructor.
|
|
*/
|
|
Listener(StackStrF & name, StackStrF & spec, uint8_t min, uint8_t max)
|
|
: Listener(name, spec, NullArray(), min, max, -1, false, false)
|
|
{
|
|
/* ... */
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Convenience constructor.
|
|
*/
|
|
Listener(StackStrF & name, StackStrF & spec, Array & tags, uint8_t min, uint8_t max)
|
|
: Listener(name, spec, tags, min, max, -1, false, false)
|
|
{
|
|
/* ... */
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Convenience constructor.
|
|
*/
|
|
Listener(StackStrF & name, StackStrF & spec, Array & tags, uint8_t min, uint8_t max, SQInteger auth)
|
|
: Listener(name, spec, tags, min, max, auth, auth >= 0, false)
|
|
{
|
|
/* ... */
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Convenience constructor.
|
|
*/
|
|
Listener(StackStrF & name, StackStrF & spec, Array & tags, uint8_t min, uint8_t max, SQInteger auth, bool prot)
|
|
: Listener(name, spec, tags, min, max, auth, prot, false)
|
|
{
|
|
/* ... */
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Base constructor.
|
|
*/
|
|
Listener(StackStrF & name, StackStrF & spec, Array & tags, uint8_t min, uint8_t max, SQInteger auth, bool prot, bool assoc)
|
|
: m_Controller()
|
|
, m_Name()
|
|
, m_ArgSpec()
|
|
, m_ArgTags()
|
|
, m_MinArgc(0)
|
|
, m_MaxArgc(SQMOD_MAX_CMD_ARGS-1)
|
|
, m_Spec()
|
|
, m_Help()
|
|
, m_Info()
|
|
, m_OnExec()
|
|
, m_OnAuth()
|
|
, m_OnPost()
|
|
, m_OnFail()
|
|
, m_Authority(ConvTo< int32_t >::From(auth))
|
|
, m_Protected(prot)
|
|
, m_Suspended(false)
|
|
, m_Associate(assoc)
|
|
, m_Prev(nullptr)
|
|
, m_Next(s_Head)
|
|
{
|
|
// Extract the given name
|
|
if ((SQ_FAILED(name.Proc())))
|
|
{
|
|
STHROWF("Unable to extract a valid listener name");
|
|
}
|
|
// Validate the specified name and assign it
|
|
m_Name.assign(ValidateName(name.mPtr), static_cast< size_t >(name.mLen));
|
|
// Initialize the specifiers to default values
|
|
for (unsigned char & n : m_ArgSpec)
|
|
{
|
|
n = CMDARG_ANY;
|
|
}
|
|
// Apply the specified argument rules/specifications
|
|
SetSpec(spec); // guaranteed the value will not be modified!
|
|
// Extract the specified argument tags
|
|
SetArgTags(tags);
|
|
// Set the specified minimum and maximum allowed arguments
|
|
SetMinArgC(min);
|
|
SetMaxArgC(max);
|
|
// Generate information for the command
|
|
GenerateInfo(false);
|
|
// We're the head element now
|
|
s_Head = this;
|
|
// Was there a previous head?
|
|
if (m_Next != nullptr)
|
|
{
|
|
// Steal previous element from previous head
|
|
m_Prev = m_Next->m_Prev;
|
|
// Did that head element had a previous element?
|
|
if (m_Prev != nullptr)
|
|
{
|
|
// Tell it we're the next element now
|
|
m_Prev->m_Next = this;
|
|
}
|
|
// Tell the previous head that we're the previous element now
|
|
m_Next->m_Prev = this;
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Copy constructor. (disabled)
|
|
*/
|
|
Listener(const Listener & o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Move constructor. (disabled)
|
|
*/
|
|
Listener(Listener && o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Destructor.
|
|
*/
|
|
~Listener()
|
|
{
|
|
// Detach the command
|
|
if (!m_Controller.Expired())
|
|
{
|
|
m_Controller.Lock()->Detach(this);
|
|
}
|
|
// Release callbacks
|
|
m_OnExec.Release();
|
|
m_OnAuth.Release();
|
|
m_OnPost.Release();
|
|
m_OnFail.Release();
|
|
// Is there an element behind us?
|
|
if (m_Prev != nullptr)
|
|
{
|
|
// Tell it to point to the element ahead of us
|
|
m_Prev->m_Next = m_Next;
|
|
}
|
|
// Is there an element ahead of us?
|
|
if (m_Next != nullptr)
|
|
{
|
|
// Tell it to point to the element behind us
|
|
m_Next->m_Prev = m_Prev;
|
|
}
|
|
// Are we the head element in the chain?
|
|
if (s_Head == this)
|
|
{
|
|
s_Head = m_Next == nullptr ? m_Prev : m_Next;
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Copy assignment operator. (disabled)
|
|
*/
|
|
Listener & operator = (const Listener & o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Move assignment operator. (disabled)
|
|
*/
|
|
Listener & operator = (Listener && o) = delete;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Used by the script engine to compare two instances of this type.
|
|
*/
|
|
SQMOD_NODISCARD int32_t Cmp(const Listener & o) const
|
|
{
|
|
if (m_Name == o.m_Name)
|
|
{
|
|
return 0;
|
|
}
|
|
else if (m_Name.size() > o.m_Name.size())
|
|
{
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Used by the script engine to convert this instance to a string.
|
|
*/
|
|
SQMOD_NODISCARD const String & ToString() const
|
|
{
|
|
return m_Name;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the number of weak references to the managed controller.
|
|
*/
|
|
SQMOD_NODISCARD uint32_t GetRefCount() const
|
|
{
|
|
return m_Controller.Count();
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Attach the listener instance to the associated command name.
|
|
*/
|
|
void Attach(const Manager & mgr)
|
|
{
|
|
// Is the associated name even valid?
|
|
if (m_Name.empty())
|
|
{
|
|
STHROWF("Invalid or empty command name");
|
|
}
|
|
// Detach from the current command controller, if any
|
|
Detach();
|
|
// Is the specified manager valid to even attempt association?
|
|
if (mgr.GetCtr())
|
|
{
|
|
// Attempt to associate with it's controller
|
|
mgr.GetCtr()->Attach(NullObject(), this);
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Detach the listener instance from the associated command name.
|
|
*/
|
|
void Detach()
|
|
{
|
|
// Make sure that we are even associated with a controller
|
|
if (!m_Controller.Expired())
|
|
{
|
|
// Detach from the associated controller
|
|
m_Controller.Lock()->Detach(this);
|
|
// Release the controller reference
|
|
m_Controller.Reset();
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* See whether the listener instance is attached to the associated command name.
|
|
*/
|
|
SQMOD_NODISCARD bool Attached() const
|
|
{
|
|
return (!m_Controller.Expired() && m_Controller.Lock()->Attached(this));
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the manager associated with this command listener instance.
|
|
*/
|
|
SQMOD_NODISCARD Object GetManager() const
|
|
{
|
|
// Are we even associated with a controller?
|
|
if (m_Controller.Expired())
|
|
{
|
|
return NullObject(); // Default to null
|
|
}
|
|
// Obtain the initial stack size
|
|
const StackGuard sg;
|
|
// Push the instance on the stack
|
|
ClassType< Manager >::PushInstance(SqVM(), m_Controller.Lock()->m_Manager);
|
|
// Grab the instance from the stack
|
|
return Var< Object >(SqVM(), -1).value;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the name that triggers this command listener instance.
|
|
*/
|
|
SQMOD_NODISCARD const String & GetName() const
|
|
{
|
|
return m_Name;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the name that triggers this command listener instance.
|
|
*/
|
|
void SetName(StackStrF & name)
|
|
{
|
|
// Extract the given name
|
|
if ((SQ_FAILED(name.Proc())))
|
|
{
|
|
STHROWF("Unable to extract a valid listener name");
|
|
}
|
|
// Validate the specified name
|
|
ValidateName(name.mPtr);
|
|
// Is this command already attached to a name?
|
|
if (!m_Controller.Expired() && m_Controller.Lock()->Attached(this))
|
|
{
|
|
const CtrRef ctr = m_Controller.Lock();
|
|
// Detach from the current name if necessary
|
|
ctr->Detach(this);
|
|
// Now it's safe to assign the new name
|
|
m_Name.assign(name.mPtr, static_cast< size_t >(name.mLen));
|
|
// We know the new name is valid
|
|
ctr->Attach(NullObject(), this);
|
|
}
|
|
else
|
|
{
|
|
m_Name.assign(name.mPtr, static_cast< size_t >(name.mLen)); // Just assign the name
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the associated user data.
|
|
*/
|
|
Object & GetData()
|
|
{
|
|
return m_Data;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Modify the associated user data.
|
|
*/
|
|
void SetData(Object & data)
|
|
{
|
|
m_Data = data;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the argument specification string.
|
|
*/
|
|
SQMOD_NODISCARD const String & GetSpec() const
|
|
{
|
|
return m_Spec;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Modify the argument specification string.
|
|
*/
|
|
void SetSpec(StackStrF & spec)
|
|
{
|
|
// Get the string
|
|
if ((SQ_FAILED(spec.Proc())))
|
|
{
|
|
STHROWF("Unable to extract a valid specifier string");
|
|
}
|
|
// Attempt to process the specified string
|
|
ProcSpec(spec.mPtr);
|
|
// Assign the specifier, if any
|
|
if (spec.mLen > 0)
|
|
{
|
|
m_Spec.assign(spec.mPtr, static_cast< size_t >(spec.mLen));
|
|
}
|
|
else
|
|
{
|
|
m_Spec.clear();
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the argument tags array.
|
|
*/
|
|
SQMOD_NODISCARD Array GetArgTags() const
|
|
{
|
|
// Allocate an array to encapsulate all tags
|
|
Array arr(SqVM(), SQMOD_MAX_CMD_ARGS);
|
|
// Put the tags to the allocated array
|
|
for (uint32_t arg = 0; arg < SQMOD_MAX_CMD_ARGS; ++arg)
|
|
{
|
|
arr.SetValue(arg, m_ArgTags[arg]);
|
|
}
|
|
// Return the array with the tags
|
|
return arr;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Modify the argument tags array.
|
|
*/
|
|
void SetArgTags(Array & tags)
|
|
{
|
|
// Preliminary checks before even attempting anything
|
|
if (tags.GetType() != OT_ARRAY || tags.IsNull())
|
|
{
|
|
for (auto & m_ArgTag : m_ArgTags)
|
|
{
|
|
m_ArgTag.clear();
|
|
}
|
|
// We're done here!
|
|
return;
|
|
}
|
|
// Attempt to retrieve the number of specified tags
|
|
const uint32_t max = ConvTo< uint32_t >::From(tags.Length());
|
|
// If no tags were specified then clear current tags
|
|
if (!max)
|
|
{
|
|
for (auto & m_ArgTag : m_ArgTags)
|
|
{
|
|
m_ArgTag.clear();
|
|
}
|
|
}
|
|
// See if we're in range
|
|
else if (max < SQMOD_MAX_CMD_ARGS)
|
|
{
|
|
// Attempt to get all arguments in one go
|
|
tags.GetArray< String >(m_ArgTags, max);
|
|
}
|
|
else
|
|
{
|
|
STHROWF("Argument tag (%u) is out of range (%d)", max, SQMOD_MAX_CMD_ARGS);
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the help message associated with this command listener instance.
|
|
*/
|
|
SQMOD_NODISCARD const String & GetHelp() const
|
|
{
|
|
return m_Help;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Modify the help message associated with this command listener instance.
|
|
*/
|
|
void SetHelp(StackStrF & help)
|
|
{
|
|
if ((SQ_FAILED(help.Proc())))
|
|
{
|
|
STHROWF("Unable to extract a valid help string");
|
|
}
|
|
else if (help.mLen > 0)
|
|
{
|
|
m_Help.assign(help.mPtr, static_cast< size_t >(help.mLen));
|
|
}
|
|
else
|
|
{
|
|
m_Help.clear();
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the informational message associated with this command listener instance.
|
|
*/
|
|
SQMOD_NODISCARD const String & GetInfo() const
|
|
{
|
|
return m_Info;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Modify the informational message associated with this command listener instance.
|
|
*/
|
|
void SetInfo(StackStrF & info)
|
|
{
|
|
if ((SQ_FAILED(info.Proc())))
|
|
{
|
|
STHROWF("Unable to extract a valid information string");
|
|
}
|
|
else if (info.mLen > 0)
|
|
{
|
|
m_Info.assign(info.mPtr, static_cast< size_t >(info.mLen));
|
|
}
|
|
else
|
|
{
|
|
m_Info.clear();
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the authority level required to execute this command listener instance.
|
|
*/
|
|
SQMOD_NODISCARD SQInteger GetAuthority() const
|
|
{
|
|
return m_Authority;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Modify the authority level required to execute this command listener instance.
|
|
*/
|
|
void SetAuthority(SQInteger level)
|
|
{
|
|
m_Authority = ConvTo< int32_t >::From(level);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* See whether this command listener instance requires explicit authority inspection.
|
|
*/
|
|
SQMOD_NODISCARD bool GetProtected() const
|
|
{
|
|
return m_Protected;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Set whether this command listener instance requires explicit authority inspection.
|
|
*/
|
|
void SetProtected(bool toggle)
|
|
{
|
|
m_Protected = toggle;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* See whether this command listener instance is currently ignoring calls.
|
|
*/
|
|
SQMOD_NODISCARD bool GetSuspended() const
|
|
{
|
|
return m_Suspended;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Set whether this command listener instance should start ignoring calls.
|
|
*/
|
|
void SetSuspended(bool toggle)
|
|
{
|
|
m_Suspended = toggle;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* See whether this command listener instance receives arguments in an associative container.
|
|
*/
|
|
SQMOD_NODISCARD bool GetAssociate() const
|
|
{
|
|
return m_Associate;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Set whether this command listener instance receives arguments in an associative container.
|
|
*/
|
|
void SetAssociate(bool toggle)
|
|
{
|
|
m_Associate = toggle;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the maximum arguments supported by this command listener.
|
|
*/
|
|
SQMOD_NODISCARD uint8_t GetMinArgC() const
|
|
{
|
|
return m_MinArgc;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Modify the minimum arguments supported by this command listener.
|
|
*/
|
|
void SetMinArgC(SQInteger val)
|
|
{
|
|
// Limit the specified number withing allowed range
|
|
val = ConvTo< uint8_t >::From(val);
|
|
// Perform a range check on the specified argument index
|
|
if (val >= SQMOD_MAX_CMD_ARGS)
|
|
{
|
|
STHROWF("Argument (%d) is out of total range (%d)", val, SQMOD_MAX_CMD_ARGS);
|
|
}
|
|
else if (static_cast< uint8_t >(val) > m_MaxArgc)
|
|
{
|
|
STHROWF("Minimum argument (%d) exceeds maximum (%u)", val, m_MaxArgc);
|
|
}
|
|
// Apply the specified value
|
|
m_MinArgc = static_cast< uint8_t >(val);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the maximum arguments supported by this command listener.
|
|
*/
|
|
SQMOD_NODISCARD SQInteger GetMaxArgC() const
|
|
{
|
|
return m_MaxArgc;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Modify the maximum arguments supported by this command listener.
|
|
*/
|
|
void SetMaxArgC(SQInteger val)
|
|
{
|
|
// Limit the specified number withing allowed range
|
|
val = ConvTo< uint8_t >::From(val);
|
|
// Perform a range check on the specified argument index
|
|
if (val >= SQMOD_MAX_CMD_ARGS)
|
|
{
|
|
STHROWF("Argument (%d) is out of total range (%d)", val, SQMOD_MAX_CMD_ARGS);
|
|
}
|
|
else if (static_cast< uint8_t >(val) < m_MinArgc)
|
|
{
|
|
STHROWF("Maximum argument (%d) exceeds minimum (%u)", val, m_MinArgc);
|
|
}
|
|
// Apply the specified value
|
|
m_MaxArgc = static_cast< uint8_t >(val);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the function that must be called when this command listener is executed.
|
|
*/
|
|
Function & GetOnExec()
|
|
{
|
|
return m_OnExec;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Modify the function that must be called when this command listener is executed.
|
|
*/
|
|
void SetOnExec(Function & func)
|
|
{
|
|
m_OnExec = std::move(func);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the function that must be called when this command listener needs to authenticate.
|
|
*/
|
|
Function & GetOnAuth()
|
|
{
|
|
return m_OnAuth;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Modify the function that must be called when this command listener needs to authenticate.
|
|
*/
|
|
void SetOnAuth(Function & func)
|
|
{
|
|
m_OnAuth = std::move(func);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the function that must be called when this command listener finished execution.
|
|
*/
|
|
Function & GetOnPost()
|
|
{
|
|
return m_OnPost;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Modify the function that must be called when this command listener finished execution.
|
|
*/
|
|
void SetOnPost(Function & func)
|
|
{
|
|
m_OnPost = std::move(func);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the function that must be called when this command listener failed execution.
|
|
*/
|
|
Function & GetOnFail()
|
|
{
|
|
return m_OnFail;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Modify the function that must be called when this command listener failed execution.
|
|
*/
|
|
void SetOnFail(Function & func)
|
|
{
|
|
m_OnFail = std::move(func);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the tag of a certain argument.
|
|
*/
|
|
SQMOD_NODISCARD const String & GetArgTag(uint32_t arg) const
|
|
{
|
|
// Perform a range check on the specified argument index
|
|
if (arg >= SQMOD_MAX_CMD_ARGS)
|
|
{
|
|
STHROWF("Argument (%u) is out of total range (%u)", arg, SQMOD_MAX_CMD_ARGS);
|
|
}
|
|
// Return the requested information
|
|
return m_ArgTags[arg];
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Modify the tag of a certain argument.
|
|
*/
|
|
void SetArgTag(uint32_t arg, StackStrF & name)
|
|
{
|
|
// Perform a range check on the specified argument index
|
|
if (arg >= SQMOD_MAX_CMD_ARGS)
|
|
{
|
|
STHROWF("Argument (%u) is out of total range (%u)", arg, SQMOD_MAX_CMD_ARGS);
|
|
}
|
|
else if ((SQ_FAILED(name.Proc())))
|
|
{
|
|
STHROWF("Unable to extract a valid argument name");
|
|
}
|
|
// The string type doesn't appreciate null values
|
|
else if (name.mLen > 0)
|
|
{
|
|
m_ArgTags[arg].assign(name.mPtr, static_cast< size_t >(name.mLen));
|
|
}
|
|
// Clear previous name in this case
|
|
else
|
|
{
|
|
m_ArgTags[arg].clear();
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Retrieve the flags of the specified argument.
|
|
*/
|
|
SQMOD_NODISCARD uint8_t GetArgFlags(uint32_t idx) const
|
|
{
|
|
// Perform a range check on the specified argument index
|
|
if (idx >= SQMOD_MAX_CMD_ARGS)
|
|
{
|
|
STHROWF("Argument (%u) is out of total range (%u)", idx, SQMOD_MAX_CMD_ARGS);
|
|
}
|
|
// Return the requested information
|
|
return m_ArgSpec[idx];
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* See whether whether the specified argument can be used on this command listener instance.
|
|
*/
|
|
SQMOD_NODISCARD bool ArgCheck(uint32_t arg, uint8_t flag) const
|
|
{
|
|
// Perform a range check on the specified argument index
|
|
if (arg >= SQMOD_MAX_CMD_ARGS)
|
|
{
|
|
STHROWF("Argument (%u) is out of total range (%u)", arg, SQMOD_MAX_CMD_ARGS);
|
|
}
|
|
// Retrieve the argument flags
|
|
const uint8_t f = m_ArgSpec[arg];
|
|
// Perform the requested check
|
|
return (f == CMDARG_ANY) || ((f & flag) != 0x0) || ((f & CMDARG_GREEDY) && (flag & CMDARG_STRING));
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* See whether the specified invoker entity has the proper authority to run this command.
|
|
*/
|
|
bool AuthCheck(const Object & invoker)
|
|
{
|
|
// Do we need explicit authority verification?
|
|
if (!m_Protected) { /* Anyone can invoke this command */ }
|
|
// Was there a custom authority inspector specified?
|
|
else if (!m_OnAuth.IsNull())
|
|
{
|
|
// Ask the specified authority inspector if this execution should be allowed
|
|
const SharedPtr< bool > ret = m_OnAuth.Evaluate< bool, const Object &, Listener * >(invoker, this);
|
|
// See what the custom authority inspector said or default to disallow
|
|
return !!ret && *ret;
|
|
}
|
|
// Was there a global authority inspector specified?
|
|
else if (!m_Controller.Expired() && !m_Controller.Lock()->GetOnAuth().IsNull())
|
|
{
|
|
// Ask the specified authority inspector if this execution should be allowed
|
|
const SharedPtr< bool > ret = m_Controller.Lock()->GetOnAuth().Evaluate< bool, const Object &, Listener * >(invoker, this);
|
|
// See what the custom authority inspector said or default to disallow
|
|
return !!ret && *ret;
|
|
}
|
|
// A negative authority level is considered to be the same as unprotected
|
|
else if (m_Authority < 0)
|
|
{
|
|
return true;
|
|
}
|
|
// Default to blocking this execution
|
|
return false;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Use the command listener argument properties to generate an informational message.
|
|
*/
|
|
void GenerateInfo(bool full);
|
|
|
|
protected:
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
typedef uint8_t ArgSpec[SQMOD_MAX_CMD_ARGS];
|
|
typedef String ArgTags[SQMOD_MAX_CMD_ARGS];
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Execute the designated callback by passing the arguments in their specified order.
|
|
*/
|
|
SQInteger Execute(const Object & invoker, const Array & args)
|
|
{
|
|
// Attempt to evaluate the specified executer knowing the manager did the validations
|
|
SharedPtr< SQInteger > ret = m_OnExec.Evaluate< SQInteger, const Object &, const Array & >(invoker, args);
|
|
// See if the executer succeeded and return the result or default to failed
|
|
return (!ret ? 0 : *ret);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Execute the designated callback by passing the arguments using an associative container.
|
|
*/
|
|
SQInteger Execute(const Object & invoker, const Table & args)
|
|
{
|
|
// Attempt to evaluate the specified executer knowing the manager did the validations
|
|
SharedPtr< SQInteger > ret = m_OnExec.Evaluate< SQInteger, const Object &, const Table & >(invoker, args);
|
|
// See if the executer succeeded and return the result or default to failed
|
|
return (!ret ? 0 : *ret);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Process the specified string and extract the argument properties in it.
|
|
*/
|
|
void ProcSpec(const SQChar * spec);
|
|
|
|
private:
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
CtrPtr m_Controller; // Manager that controls this command listener.
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
String m_Name; // Name of the command that triggers this listener.
|
|
Object m_Data; // Arbitrary user data associated with this particular instance.
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
ArgSpec m_ArgSpec; // List of argument type specifications.
|
|
ArgTags m_ArgTags; // List of argument tags/names.
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
uint8_t m_MinArgc; // Minimum number of arguments supported by this listener.
|
|
uint8_t m_MaxArgc; // Maximum number of arguments supported by this listener.
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
String m_Spec; // String used to generate the argument type specification list.
|
|
String m_Help; // String describing what the command is supposed to do.
|
|
String m_Info; // String with syntax information for the command.
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
Function m_OnExec; // Function to call when the command is executed.
|
|
Function m_OnAuth; // Function to call when the invoker must be authenticated.
|
|
Function m_OnPost; // Function to call after the command was successfully executed.
|
|
Function m_OnFail; // Function to call after the command execution failed.
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
int32_t m_Authority; // Built-in authority level required to execute this command.
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
bool m_Protected; // Whether explicit authentication of the invoker is required.
|
|
bool m_Suspended; // Whether this command should block further invocations.
|
|
bool m_Associate; // Whether arguments are sent as table instead of array.
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
Listener * m_Prev; // Previous listener in the chain.
|
|
Listener * m_Next; // Next listener in the chain.
|
|
|
|
// --------------------------------------------------------------------------------------------
|
|
static Listener * s_Head; // The head of the listener chain.
|
|
};
|
|
|
|
} // Namespace:: Cmd
|
|
} // Namespace:: SqMod
|