Skip to content

Commit

Permalink
Refactoring, move pybind11-related code to the PythonModule type.
Browse files Browse the repository at this point in the history
  • Loading branch information
codereader committed Jan 27, 2024
1 parent 89b0289 commit 2382c8d
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 102 deletions.
90 changes: 90 additions & 0 deletions plugins/script/PythonModule.cpp
Expand Up @@ -7,7 +7,12 @@

#include <pybind11/embed.h>
#include <pybind11/stl_bind.h>
#include <pybind11/attr.h>
#include <pybind11/eval.h>

#include "itextstream.h"
#include "os/file.h"
#include "os/path.h"

namespace script
{
Expand Down Expand Up @@ -129,6 +134,41 @@ ExecutionResultPtr PythonModule::executeString(const std::string& scriptString)
return result;
}

void PythonModule::executeScriptFile(const std::string& scriptBasePath, const std::string& relativeScriptPath, bool setExecuteCommandAttr)
{
try
{
auto fullPath = scriptBasePath + relativeScriptPath;

// Prevent calling exec_file with a non-existent file, we would
// get crashes during Py_Finalize later on.
if (!os::fileOrDirExists(fullPath))
{
rError() << "Error: File " << fullPath << " doesn't exist." << std::endl;
return;
}

py::dict locals;

if (setExecuteCommandAttr)
{
locals["__executeCommand__"] = true;
}

// Attempt to run the specified script
py::eval_file(fullPath, getGlobals(), locals);
}
catch (std::invalid_argument& e)
{
rError() << "Error trying to execute file " << relativeScriptPath << ": " << e.what() << std::endl;
}
catch (const py::error_already_set& ex)
{
rError() << "Error while executing file: " << relativeScriptPath << ": " << std::endl;
rError() << ex.what() << std::endl;
}
}

py::dict& PythonModule::getGlobals()
{
if (!_globals)
Expand Down Expand Up @@ -233,6 +273,56 @@ bool PythonModule::interfaceExists(const std::string& name)
return false;
}

ScriptCommand::Ptr PythonModule::createScriptCommand(const std::string& scriptBasePath, const std::string& relativeScriptPath)
{
try
{
auto fullPath = scriptBasePath + relativeScriptPath;

// Create a new dictionary for the initialisation routine
py::dict locals;

// Disable the flag for initialisation, just to be sure
locals["__executeCommand__"] = false;

// Attempt to run the specified script
py::eval_file(fullPath, getGlobals(), locals);

std::string cmdName;
std::string cmdDisplayName;

if (locals.contains("__commandName__"))
{
cmdName = locals["__commandName__"].cast<std::string>();
}

if (locals.contains("__commandDisplayName__"))
{
cmdDisplayName = locals["__commandDisplayName__"].cast<std::string>();
}

if (!cmdName.empty())
{
if (cmdDisplayName.empty())
{
cmdDisplayName = cmdName;
}

// Successfully retrieved the command
return std::make_shared<ScriptCommand>(cmdName, cmdDisplayName, relativeScriptPath);
}

rError() << "Script file " << relativeScriptPath << " does not export a __commandName__ value" << std::endl;
return {};
}
catch (const py::error_already_set& ex)
{
rError() << "Script file " << relativeScriptPath << " is not a valid command:" << std::endl;
rError() << ex.what() << std::endl;
return {};
}
}

PythonModule* PythonModule::_instance = nullptr;

}
8 changes: 8 additions & 0 deletions plugins/script/PythonModule.h
Expand Up @@ -7,6 +7,7 @@
#include <pybind11/pybind11.h>

#include "PythonConsoleWriter.h"
#include "ScriptCommand.h"

namespace py = pybind11;

Expand Down Expand Up @@ -50,11 +51,18 @@ class PythonModule final

ExecutionResultPtr executeString(const std::string& scriptString);

// Execute the given script file
void executeScriptFile(const std::string& scriptBasePath, const std::string& relativeScriptPath, bool setExecuteCommandAttr);

// Get the global object dictionary of this module
py::dict& getGlobals();

void addInterface(const NamedInterface& iface);

// Attempts to create a script command from the given .py file
// Will return an empty object if the file path is not a valid file
ScriptCommand::Ptr createScriptCommand(const std::string& scriptBasePath, const std::string& relativeScriptPath);

private:
// Register the darkradiant module with the inittab pointing to InitModule
void registerModule();
Expand Down
9 changes: 3 additions & 6 deletions plugins/script/ScriptCommand.h
Expand Up @@ -2,7 +2,6 @@

#include "iscript.h"
#include <string>
#include <map>
#include <memory>

namespace script
Expand All @@ -25,11 +24,13 @@ class ScriptCommand :
std::string _scriptFilename;

public:
using Ptr = std::shared_ptr<ScriptCommand>;

ScriptCommand(const std::string& name,
const std::string& displayName,
const std::string& scriptFilename);

virtual ~ScriptCommand();
~ScriptCommand() override;

const std::string& getName() const override
{
Expand All @@ -46,9 +47,5 @@ class ScriptCommand :
return _displayName;
}
};
typedef std::shared_ptr<ScriptCommand> ScriptCommandPtr;

// A mapping of named script commands
typedef std::map<std::string, ScriptCommandPtr> ScriptCommandMap;

} // namespace script
119 changes: 24 additions & 95 deletions plugins/script/ScriptingSystem.cpp
@@ -1,12 +1,6 @@
#include "ScriptingSystem.h"

#include <pybind11/pybind11.h>
#include <pybind11/attr.h>
#include <pybind11/eval.h>

#include "i18n.h"
#include "itextstream.h"
#include "iradiant.h"
#include "ui/imainframe.h"
#include "iundo.h"

Expand Down Expand Up @@ -41,7 +35,6 @@
#include "SceneNodeBuffer.h"

#include "os/fs.h"
#include "os/file.h"
#include "os/path.h"
#include <functional>
#include "string/case_conv.h"
Expand All @@ -65,37 +58,7 @@ void ScriptingSystem::executeScriptFile(const std::string& filename)

void ScriptingSystem::executeScriptFile(const std::string& filename, bool setExecuteCommandAttr)
{
try
{
std::string filePath = _scriptPath + filename;

// Prevent calling exec_file with a non-existent file, we would
// get crashes during Py_Finalize later on.
if (!os::fileOrDirExists(filePath))
{
rError() << "Error: File " << filePath << " doesn't exist." << std::endl;
return;
}

py::dict locals;

if (setExecuteCommandAttr)
{
locals["__executeCommand__"] = true;
}

// Attempt to run the specified script
py::eval_file(filePath, _pythonModule->getGlobals(), locals);
}
catch (std::invalid_argument& e)
{
rError() << "Error trying to execute file " << filename << ": " << e.what() << std::endl;
}
catch (const py::error_already_set& ex)
{
rError() << "Error while executing file: " << filename << ": " << std::endl;
rError() << ex.what() << std::endl;
}
_pythonModule->executeScriptFile(_scriptPath, filename, setExecuteCommandAttr);
}

ExecutionResultPtr ScriptingSystem::executeString(const std::string& scriptString)
Expand Down Expand Up @@ -162,7 +125,7 @@ void ScriptingSystem::executeCommand(const std::string& name)
}

// Lookup the name
ScriptCommandMap::iterator found = _commands.find(name);
auto found = _commands.find(name);

if (found == _commands.end())
{
Expand All @@ -178,62 +141,28 @@ void ScriptingSystem::executeCommand(const std::string& name)

void ScriptingSystem::loadCommandScript(const std::string& scriptFilename)
{
try
{
// Create a new dictionary for the initialisation routine
py::dict locals;

// Disable the flag for initialisation, just for sure
locals["__executeCommand__"] = false;

// Attempt to run the specified script
py::eval_file(_scriptPath + scriptFilename, _pythonModule->getGlobals(), locals);

std::string cmdName;
std::string cmdDisplayName;

if (locals.contains("__commandName__"))
{
cmdName = locals["__commandName__"].cast<std::string>();
}

if (locals.contains("__commandDisplayName__"))
{
cmdDisplayName = locals["__commandDisplayName__"].cast<std::string>();
}

if (!cmdName.empty())
{
if (cmdDisplayName.empty())
{
cmdDisplayName = cmdName;
}

// Successfully retrieved the command
auto cmd = std::make_shared<ScriptCommand>(cmdName, cmdDisplayName, scriptFilename);

// Try to register this named command
auto result = _commands.insert(std::make_pair(cmdName, cmd));

// Result.second is TRUE if the insert succeeded
if (result.second)
{
rMessage() << "Registered script file " << scriptFilename
<< " as " << cmdName << std::endl;
}
else
{
rError() << "Error in " << scriptFilename << ": Script command "
<< cmdName << " has already been registered in "
<< _commands[cmdName]->getFilename() << std::endl;
}
}
}
catch (const py::error_already_set& ex)
{
rError() << "Script file " << scriptFilename << " is not a valid command:" << std::endl;
rError() << ex.what() << std::endl;
}
auto command = _pythonModule->createScriptCommand(_scriptPath, scriptFilename);

if (!command)
{
// The python module already emitted some errors to the log, just exit
return;
}

// Try to register this named command
auto result = _commands.emplace(command->getName(), command);

// Result.second is TRUE if the insert succeeded
if (result.second)
{
rMessage() << "Registered script file " << scriptFilename << " as " << command->getName() << std::endl;
}
else
{
rError() << "Error in " << scriptFilename << ": Script command "
<< command->getName() << " has already been registered in "
<< _commands[command->getName()]->getFilename() << std::endl;
}
}

void ScriptingSystem::reloadScripts()
Expand Down
2 changes: 1 addition & 1 deletion plugins/script/ScriptingSystem.h
Expand Up @@ -29,7 +29,7 @@ class ScriptingSystem :
std::string _scriptPath;

// All named script commands (pointing to .py files)
ScriptCommandMap _commands;
std::map<std::string, ScriptCommand::Ptr> _commands;

sigc::signal<void> _sigScriptsReloaded;

Expand Down

0 comments on commit 2382c8d

Please sign in to comment.