Cytopia  0.3
A city building simulation game
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
ScriptEngine.cxx
Go to the documentation of this file.
1 #include "ScriptEngine.hxx"
2 #include "LOG.hxx"
3 
4 // Include the definitions of the script library and the add-ons we'll use.
5 // The project settings may need to be configured to let the compiler where
6 // to find these headers. Don't forget to add the source modules for the
7 // add-ons to your project as well so that they will be compiled into the
8 // application.
9 
10 #include <Filesystem.hxx>
11 
12 #include <angelscript.h>
13 #include <scriptstdstring/scriptstdstring.h>
14 #include <scriptarray/scriptarray.h>
15 #include <scriptdictionary/scriptdictionary.h>
16 #include <scriptmath/scriptmath.h>
17 #include <scriptmath/scriptmathcomplex.h>
18 #include <scriptbuilder/scriptbuilder.h>
19 #include <cassert>
20 #include <string>
21 #include <SDL.h>
22 
23 using namespace std;
24 
25 void print(const string &str) { LOG(LOG_INFO) << "[AS] " << str; }
26 
27 ScriptEngine::~ScriptEngine()
28 {
29  // Clean up
30  if (context)
31  context->Release();
32  if (engine)
33  engine->ShutDownAndRelease();
34 }
35 
36 void ScriptEngine::init()
37 {
38  // Create the script engine
39  engine = asCreateScriptEngine();
40  engine->SetEngineProperty(asEP_ALLOW_UNSAFE_REFERENCES, true); // Needed for ImGui
41 
42  // Set the message callback to receive information on errors in human readable form.
43  int r = engine->SetMessageCallback(asMETHOD(ScriptEngine, messageCallback), this, asCALL_THISCALL);
44  assert(r >= 0);
45 
46  // AngelScript doesn't have a built-in string type, as there is no definite standard
47  // string type for C++ applications. Every developer is free to register its own string type.
48  // The SDK do however provide a standard add-on for registering a string type, so it's not
49  // necessary to implement the registration yourself if you don't want to.
50  RegisterStdString(engine);
51  RegisterScriptArray(engine, true);
52  RegisterScriptDictionary(engine);
53  RegisterScriptMath(engine);
54  RegisterScriptMathComplex(engine);
55 
56  // Register our own functions
57  registerImVec2(engine);
58  registerImGuiBindings(engine);
59 
60  // Register the function that we want the scripts to call
61  r = engine->RegisterGlobalFunction("void print(const string &in)", asFUNCTION(print), asCALL_CDECL);
62  assert(r >= 0);
63 
64  context = engine->CreateContext();
65 }
66 
67 int ScriptEngine::framestep(float dt)
68 {
69  // framestep stuff below
70  if (!engine || !context)
71  return 0;
72 
73  for (auto &m_addon_scrip : m_addon_scrips)
74  {
75  if (!m_addon_scrip.frameStepFunctionPtr)
76  {
77  continue;
78  }
79 
80  context->Prepare(m_addon_scrip.frameStepFunctionPtr);
81 
82  // Set the function arguments
83  context->SetArgFloat(0, dt);
84 
85  int r = context->Execute();
86  if (r == asEXECUTION_FINISHED)
87  {
88  // The return value is only valid if the execution finished successfully
89  asDWORD ret = context->GetReturnDWord();
90  }
91  }
92  return 0;
93 }
94 
95 int ScriptEngine::loadScript(const string &scriptName, ScriptCategory category /* = ScriptCategory::ADDON*/)
96 {
97  // This function creates a new script unit, tries to set it up and removes it if setup fails.
98  // -----------------------------------------------------------------------------------------
99  // A script unit is how Rigs of Rods organizes scripts from various sources.
100  // Because the script is executed during loading, it's wrapping unit must
101  // be created early, and removed if setup fails.
102  int unit_id = (int)m_addon_scrips.size();
103  m_addon_scrips.resize(m_addon_scrips.size() + 1);
104  m_addon_scrips[unit_id].scriptName = scriptName;
105  m_addon_scrips[unit_id].scriptCategory = category;
106 
107  // Perform the actual script loading, building and running main().
108  int result = this->setupScriptUnit(unit_id);
109 
110  // If setup failed, remove the unit.
111  if (result != 0)
112  {
113  m_addon_scrips.pop_back();
114  }
115 
116  return result;
117 }
118 
119 int ScriptEngine::setupScriptUnit(int unit_id)
120 {
121  int result = 0;
122  if (!engine || !context)
123  return -2;
124 
125  string moduleName = this->composeModuleName(m_addon_scrips[unit_id].scriptName, m_addon_scrips[unit_id].scriptCategory);
126  if (moduleName.empty())
127  return -1;
128 
129  // The builder is a helper class that will load the script file,
130  // search for #include directives, and load any included files as
131  // well.
132  CScriptBuilder builder;
133 
134  // A script module is how AngelScript organizes scripts.
135  // It contains the script loaded by user plus all `#include`-d scripts.
136  result = builder.StartNewModule(engine, moduleName.c_str());
137  if (result < 0)
138  {
139  LOG(LOG_ERROR) << "Could not load script " << moduleName << " - failed to create module.";
140  return result;
141  }
142  m_addon_scrips[unit_id].scriptModule = engine->GetModule(moduleName.c_str(), asGM_ONLY_IF_EXISTS);
143 
144  // Load the script from the file system.
145  result = builder.AddSectionFromFile(m_addon_scrips[unit_id].scriptName.c_str());
146  if (result < 0)
147  {
148  LOG(LOG_ERROR) << "Could not load script " << moduleName << " - failed to process file.";
149  return result;
150  }
151 
152  result = builder.BuildModule();
153  if (result < 0)
154  {
155  LOG(LOG_ERROR) << "Could not load script " << moduleName << " - failed to build module.";
156  return result;
157  }
158 
159  // get some other optional functions
160  m_addon_scrips[unit_id].frameStepFunctionPtr = m_addon_scrips[unit_id].scriptModule->GetFunctionByDecl("void frameStep(float)");
161 
162  m_addon_scrips[unit_id].eventCallbackFunctionPtr =
163  m_addon_scrips[unit_id].scriptModule->GetFunctionByDecl("void eventCallback(int, int)");
164 
165  m_addon_scrips[unit_id].defaultEventCallbackFunctionPtr =
166  m_addon_scrips[unit_id].scriptModule->GetFunctionByDecl("void defaultEventCallback(int, string, string, int)");
167 
168  // Find the function that is to be called.
169  auto main_func = m_addon_scrips[unit_id].scriptModule->GetFunctionByDecl("void main()");
170  if (main_func == nullptr)
171  {
172  // The function couldn't be found. Instruct the script writer to include the
173  // expected function in the script.
174  LOG(LOG_ERROR) << "Could not load script " << moduleName << " - there is no function `main()";
175  return 0;
176  }
177 
178  // Prepare the script context with the function we wish to execute. Prepare()
179  // must be called on the context before each new script function that will be
180  // executed. Note, that if you intend to execute the same function several
181  // times, it might be a good idea to store the function id returned by
182  // GetFunctionIDByDecl(), so that this relatively slow call can be skipped.
183  result = context->Prepare(main_func);
184  if (result < 0)
185  {
186  LOG(LOG_ERROR) << "Could not load script " << moduleName << " - failed to build module.";
187  context->Release();
188  return -1;
189  }
190 
191  // Execute the `main()` function in the script.
192  // The function must have full access to the game API.
193  LOG(LOG_DEBUG) << "Executing main() in " << moduleName;
194  result = context->Execute();
195  if (result != asEXECUTION_FINISHED)
196  {
197  // The execution didn't complete as expected. Determine what happened.
198  if (result == asEXECUTION_ABORTED)
199  {
200  LOG(LOG_DEBUG) << "The script was aborted before it could finish. Probably it timed out.";
201  }
202  else if (result == asEXECUTION_EXCEPTION)
203  {
204  // An exception occurred, let the script writer know what happened so it can be corrected.
205  LOG(LOG_ERROR) << "An exception '" << context->GetExceptionString() << "' occurred. Please correct the code in file '"
206  << m_addon_scrips[unit_id].scriptName << "' and try again.";
207 
208  // Write some information about the script exception
209  asIScriptFunction *func = context->GetExceptionFunction();
210  LOG(LOG_DEBUG) << "func: " << func->GetDeclaration();
211  LOG(LOG_DEBUG) << "modl: " << func->GetModuleName();
212  LOG(LOG_DEBUG) << "sect: " << func->GetScriptSectionName();
213  LOG(LOG_DEBUG) << "line: " << context->GetExceptionLineNumber();
214  LOG(LOG_DEBUG) << "desc: " << context->GetExceptionString();
215  }
216  else
217  {
218  LOG(LOG_DEBUG) << "The script ended for some unforeseen reason " << result;
219  }
220 
221  LOG(LOG_ERROR) << "Could not load script " << moduleName << " - error running function `main()`, check AngelScript.log";
222  }
223  else
224  {
225  LOG(LOG_DEBUG) << "The script finished successfully.";
226  }
227 
228  return 0;
229 }
230 
231 void ScriptEngine::unloadScript(const string &scriptName, ScriptCategory category)
232 {
233  string module_name = this->composeModuleName(scriptName, category);
234  if (module_name.empty())
235  return;
236 
237  for (size_t i = 0; i < m_addon_scrips.size(); i++)
238  {
239  if (m_addon_scrips[i].scriptModule->GetName() == module_name)
240  {
241  m_addon_scrips.erase(m_addon_scrips.begin() + i);
242  }
243  }
244 }
245 
246 string ScriptEngine::composeModuleName(string const &scriptName, ScriptCategory origin)
247 {
248  switch (origin)
249  {
250  case ScriptCategory::BUILD_IN:
251  return "BUILD_IN: " + scriptName;
252 
253  case ScriptCategory::ADDON:
254  return "ADDON: " + scriptName;
255 
256  case ScriptCategory::CUSTOM:
257  return "CUSTOM: " + scriptName;
258 
259  default:
260  return "";
261  }
262 }
263 
264 void ScriptEngine::messageCallback(const asSMessageInfo *msg)
265 {
266  switch (msg->type)
267  {
268  case asMSGTYPE_INFORMATION:
269  LOG(LOG_INFO) << msg->section << " (" << msg->row << "," << msg->col << ") " << msg->message;
270  break;
271 
272  case asMSGTYPE_WARNING:
273  LOG(LOG_WARNING) << msg->section << " (" << msg->row << "," << msg->col << ") " << msg->message;
274  break;
275 
276  case asMSGTYPE_ERROR:
277  LOG(LOG_ERROR) << msg->section << " (" << msg->row << "," << msg->col << ") " << msg->message;
278  break;
279 
280  default:
281  LOG(LOG_DEBUG) << msg->section << " (" << msg->row << "," << msg->col << ") " << msg->message;
282  break;
283  }
284 }
registerImVec2
void registerImVec2(asIScriptEngine *engine)
Definition: ImGuiAngelscript.cxx:24
LOG
Definition: LOG.hxx:32
LOG_INFO
@ LOG_INFO
Definition: LOG.hxx:25
int
int
Definition: tileData.hxx:57
LOG.hxx
ScriptEngine.hxx
LOG_ERROR
@ LOG_ERROR
Definition: LOG.hxx:28
LOG_DEBUG
@ LOG_DEBUG
Definition: LOG.hxx:26
print
void print(const string &str)
Definition: ScriptEngine.cxx:25
Filesystem.hxx
std
Definition: Point.hxx:83
registerImGuiBindings
void registerImGuiBindings(asIScriptEngine *engine)
Definition: ImGuiAngelscript.cxx:61
LOG_WARNING
@ LOG_WARNING
Definition: LOG.hxx:27