Leviathan  0.8.0.0
Leviathan game engine
ScriptSystemWrapper.cpp
Go to the documentation of this file.
1 // ------------------------------------ //
2 #include "ScriptSystemWrapper.h"
3 
4 #include "GameWorld.h"
8 
9 #include "add_on/scriptarray/scriptarray.h"
10 using namespace Leviathan;
11 // ------------------------------------ //
13  const std::string& name, asIScriptObject* impl) :
14  Name(name),
15  ImplementationObject(impl)
16 {
17  if(!ImplementationObject)
18  throw InvalidArgument("ScriptSystemWrapper not given an angelscript object");
19 }
20 
22 {
24 
25  if(ImplementationObject) {
26  LOG_ERROR("ScriptSystemWrapper: Release has not been called before destructor");
27  ImplementationObject->Release();
28  }
29 }
30 // ------------------------------------ //
32 {
33  if(ImplementationObject)
34  ImplementationObject->AddRef();
35  return ImplementationObject;
36 }
37 // ------------------------------------ //
39 {
40  asIScriptFunction* func = ImplementationObject->GetObjectType()->GetMethodByName("Init");
41 
42  if(!func) {
43 
44  LOG_ERROR("Script system(" + Name + "): failed to find Init method on as object");
45  return;
46  }
47 
48  ScriptRunningSetup setup;
49  auto result =
50  static_cast<ScriptExecutor*>(ImplementationObject->GetEngine()->GetUserData())
51  ->RunScriptMethod<void>(setup, func, ImplementationObject, world);
52 
53  if(result.Result != SCRIPT_RUN_RESULT::Success) {
54 
55  LOG_ERROR("Script system(" + Name + "): failed to call Init");
56  return;
57  }
58 }
60 {
61  asIScriptFunction* func =
62  ImplementationObject->GetObjectType()->GetMethodByName("Release");
63 
64  if(!func) {
65 
66  LOG_ERROR("Script system(" + Name + "): failed to find Release method on as object");
67  return;
68  }
69 
70  ScriptRunningSetup setup;
71  auto result =
72  static_cast<ScriptExecutor*>(ImplementationObject->GetEngine()->GetUserData())
73  ->RunScriptMethod<void>(setup, func, ImplementationObject);
74 
75  if(result.Result != SCRIPT_RUN_RESULT::Success) {
76 
77  LOG_ERROR("Script system(" + Name + "): failed to call Release");
78  return;
79  }
80 
82 
83  ImplementationObject->Release();
84  ImplementationObject = nullptr;
85 }
86 // ------------------------------------ //
88 {
89  // For performance reasons this might be good to store
90  if(!RunMethod) {
91 
92  RunMethod = ImplementationObject->GetObjectType()->GetMethodByName("Run");
93  RunMethod->AddRef();
94  }
95 
96  if(!RunMethod) {
97 
98  LOG_ERROR("Script system(" + Name + "): failed to find Run method on as object");
99  return;
100  }
101 
102  ScriptRunningSetup setup;
103  auto result =
104  static_cast<ScriptExecutor*>(ImplementationObject->GetEngine()->GetUserData())
105  ->RunScriptMethod<void>(setup, RunMethod, ImplementationObject, elapsed);
106 
107  if(result.Result != SCRIPT_RUN_RESULT::Success) {
108 
109  LOG_ERROR("Script system(" + Name + "): failed to call Run");
110  return;
111  }
112 }
113 // ------------------------------------ //
115 {
116  // For performance reasons this might be good to store
117  if(!CreateAndDestroyNodesMethod) {
118 
119  CreateAndDestroyNodesMethod =
120  ImplementationObject->GetObjectType()->GetMethodByName("CreateAndDestroyNodes");
121  CreateAndDestroyNodesMethod->AddRef();
122  }
123 
124  if(!CreateAndDestroyNodesMethod) {
125 
126  LOG_ERROR("Script system(" + Name +
127  "): failed to find CreateAndDestroyNodes method on as object");
128  return;
129  }
130 
131  ScriptRunningSetup setup;
132  auto result =
133  static_cast<ScriptExecutor*>(ImplementationObject->GetEngine()->GetUserData())
134  ->RunScriptMethod<void>(setup, CreateAndDestroyNodesMethod, ImplementationObject);
135 
136  if(result.Result != SCRIPT_RUN_RESULT::Success) {
137 
138  LOG_ERROR("Script system(" + Name + "): failed to call CreateAndDestroyNodes");
139  return;
140  }
141 }
142 // ------------------------------------ //
144 {
145  asIScriptFunction* func = ImplementationObject->GetObjectType()->GetMethodByName("Clear");
146 
147  if(!func) {
148 
149  LOG_ERROR("Script system(" + Name + "): failed to find Clear method on as object");
150  return;
151  }
152 
153  ScriptRunningSetup setup;
154  auto result =
155  static_cast<ScriptExecutor*>(ImplementationObject->GetEngine()->GetUserData())
156  ->RunScriptMethod<void>(setup, func, ImplementationObject);
157 
158  if(result.Result != SCRIPT_RUN_RESULT::Success) {
159 
160  LOG_ERROR("Script system(" + Name + "): failed to call Clear");
161  return;
162  }
163 }
164 
166 {
167  asIScriptFunction* func =
168  ImplementationObject->GetObjectType()->GetMethodByName("Suspend");
169 
170  // This is optional
171  if(!func)
172  return;
173 
174  ScriptRunningSetup setup;
175  auto result =
176  static_cast<ScriptExecutor*>(ImplementationObject->GetEngine()->GetUserData())
177  ->RunScriptMethod<void>(setup, func, ImplementationObject);
178 
179  if(result.Result != SCRIPT_RUN_RESULT::Success) {
180 
181  LOG_ERROR("Script system(" + Name + "): failed to call Suspend");
182  return;
183  }
184 }
185 
187 {
188  asIScriptFunction* func = ImplementationObject->GetObjectType()->GetMethodByName("Resume");
189 
190  // This is optional
191  if(!func)
192  return;
193 
194  ScriptRunningSetup setup;
195  auto result =
196  static_cast<ScriptExecutor*>(ImplementationObject->GetEngine()->GetUserData())
197  ->RunScriptMethod<void>(setup, func, ImplementationObject);
198 
199  if(result.Result != SCRIPT_RUN_RESULT::Success) {
200 
201  LOG_ERROR("Script system(" + Name + "): failed to call Resume");
202  return;
203  }
204 }
205 // ------------------------------------ //
207 {
208 
209  if(RunMethod) {
210  RunMethod->Release();
211  RunMethod = nullptr;
212  }
213 
214  if(CreateAndDestroyNodesMethod) {
215  CreateAndDestroyNodesMethod->Release();
216  CreateAndDestroyNodesMethod = nullptr;
217  }
218 }
219 // ------------------------------------ //
220 // ScriptSystemNodeHelper
223 inline bool GetCachedScriptObjectIDAtIndex(CScriptArray* cached, asUINT index,
224  asIScriptContext* context, ScriptExecutor* exec, ObjectID& id)
225 {
226  asIScriptObject* obj = *static_cast<asIScriptObject**>(cached->At(index));
227 
228  if(obj->GetPropertyCount() < 1) {
229 
230  context->SetException(("cachedcomponents number " + std::to_string(index) +
231  " doesn't have 'ObjectID id' as its first property")
232  .c_str());
233  return false;
234  }
235 
236  // This check can be disabled once this is working (as reading 4 bytes will most
237  // often probably work, but for developing new systems this check is good)
238  const auto propertyType = obj->GetPropertyTypeId(0);
239 
240  const auto neededType = AngelScriptTypeIDResolver<ObjectID>::Get(exec);
241 
242  if(propertyType != neededType) {
243 
244  context->SetException(("cachedcomponents number " + std::to_string(index) +
245  " doesn't have 'ObjectID id' as its first property. Type " +
246  std::to_string(propertyType) +
247  " doesn't match ObjectID type: " + std::to_string(neededType))
248  .c_str());
249  return false;
250  }
251 
252  // Now that the property is (probably) validated we can read it
253  ObjectID* idProperty = static_cast<ObjectID*>(obj->GetAddressOfProperty(0));
254 
255  id = *idProperty;
256  return true;
257 }
258 
260 
261  ComponentFindStatusForCache(asIScriptObject* as) :
262  ScriptComponent(as), CType(-1, -1), IsScript(true)
263  {}
265  CppComponent(cpp), CType(info), IsScript(false)
266  {}
267 
268  union {
270  asIScriptObject* ScriptComponent;
271  };
272 
274  bool IsScript;
275 };
276 
277 
280 inline bool TryToCreateNewCachedComponentsForEntity(ObjectID newentity, CScriptArray* cached,
281  asIScriptFunction* factoryfunc,
282  std::vector<std::tuple<void*, ObjectID, ComponentTypeInfo>>& addedcpp,
283  std::vector<std::tuple<asIScriptObject*, ObjectID, ScriptComponentHolder*>>& addedscript,
284  GameWorld* world, CScriptArray& systemcomponents, asIScriptContext* context,
285  ScriptExecutor* exec)
286 {
287  // Find the needed components //
288  std::vector<ComponentFindStatusForCache> foundComponents;
289 
290  const auto sysSize = systemcomponents.GetSize();
291 
292  foundComponents.reserve(sysSize);
293 
294  for(asUINT i = 0; i < sysSize; ++i) {
295 
296  ScriptSystemUses* type = static_cast<ScriptSystemUses*>(systemcomponents.At(i));
297 
298  bool found = false;
299 
300  if(type->UsesName) {
301 
302  // First search added //
303  for(const auto& addedTuple : addedscript) {
304 
305  if(std::get<1>(addedTuple) != newentity ||
306  std::get<2>(addedTuple)->ComponentType != type->Name)
307  continue;
308 
309  // Found //
310  foundComponents.push_back(std::get<0>(addedTuple));
311  found = true;
312  break;
313  }
314 
315  // And then do full search //
316  if(!found) {
317 
318  auto* holder = world->GetScriptComponentHolder(type->Name);
319 
320  if(!holder) {
321 
322  context->SetException(
323  ("systemcomponents has type that world doesn't have: " + type->Name)
324  .c_str());
325  return false;
326  }
327 
328  asIScriptObject* fullSearchResult = holder->Find(newentity);
329  holder->Release();
330 
331  if(fullSearchResult) {
332 
333  // We don't need to keep a reference as the holder will do that for us //
334  fullSearchResult->Release();
335  foundComponents.push_back(fullSearchResult);
336  found = true;
337  }
338  }
339 
340 
341  } else {
342 
343  // First search added //
344  for(const auto& addedTuple : addedcpp) {
345 
346  if(std::get<1>(addedTuple) != newentity ||
347  std::get<2>(addedTuple).LeviathanType != type->Type)
348  continue;
349 
350  // Found //
351  foundComponents.push_back({std::get<0>(addedTuple), std::get<2>(addedTuple)});
352  found = true;
353  break;
354  }
355 
356  // And then do full search //
357  if(!found) {
358 
359  const auto existingComponent = world->GetComponentWithType(
360  newentity, static_cast<COMPONENT_TYPE>(type->Type));
361 
362  if(std::get<0>(existingComponent)) {
363 
364  foundComponents.push_back(
365  {std::get<0>(existingComponent), std::get<1>(existingComponent)});
366  found = true;
367  }
368  }
369  }
370 
371  if(!found) {
372  // Fail if not found //
373  return true;
374  }
375  }
376 
377  // Skip if already exists //
378  bool exists = false;
379 
380  for(asUINT i = 0; i < cached->GetSize(); ++i) {
381 
382  ObjectID currentID;
383  if(!GetCachedScriptObjectIDAtIndex(cached, i, context, exec, currentID))
384  return false;
385 
386  if(currentID == newentity) {
387 
388  exists = true;
389  break;
390  }
391  }
392 
393  if(exists)
394  return true;
395 
396  // Call factory to create it //
397  auto scriptRunInfo = exec->PrepareCustomScriptRun(factoryfunc);
398 
399  if(scriptRunInfo) {
400 
401  // Pass parameters //
402  // TODO: the types here should be checked only once and then just assumed to be right
403  // for more performance
404 
405  if(!PassParameterToCustomRun(scriptRunInfo, static_cast<uint32_t>(newentity))) {
406 
407  context->SetException(("failed to pass ObjectID as first param to factory func: " +
408  std::string(factoryfunc->GetDeclaration()))
409  .c_str());
410  return false;
411  }
412 
413  // Then the rest //
414  for(const auto& component : foundComponents) {
415 
416  bool success = false;
417 
418  if(component.IsScript) {
419 
420  success = PassParameterToCustomRun(scriptRunInfo, component.ScriptComponent);
421 
422  } else {
423  success = PassParameterToCustomRun(
424  scriptRunInfo, component.CppComponent, component.CType.AngelScriptType);
425  }
426 
427  if(!success) {
428 
429  context->SetException(
430  ("failed to pass parameter number " +
431  std::to_string(scriptRunInfo->PassedIndex) +
432  " to factory func: " + std::string(factoryfunc->GetDeclaration()))
433  .c_str());
434  return false;
435  }
436  }
437  }
438 
439  auto result = exec->ExecuteCustomRun<asIScriptObject*>(scriptRunInfo);
440 
441  if(result.Result != SCRIPT_RUN_RESULT::Success || result.Value == nullptr) {
442 
443  context->SetException(("failed to create new cachedcomponent, factory function failed "
444  "to run or returned null, func: " +
445  std::string(factoryfunc->GetDeclaration()))
446  .c_str());
447  return false;
448  }
449 
450  // And then store it in cached //
451  // The array increments reference count
452  // handle type so we need to give this a pointer to a pointer
453  cached->InsertLast(&result.Value);
454  return true;
455 }
456 
458  GameWorld* world, void* cachedcomponents, int cachedtypeid, CScriptArray& systemcomponents)
459 {
460  asIScriptContext* context = asGetActiveContext();
461 
462  if(!context)
463  throw InvalidState(
464  "ScriptSystemNodeHelper: not called from a script function (no active context)");
465 
466  if(!world) {
467 
468  context->SetException("ScriptSystemNodeHelper: world reference is null");
469  return;
470  }
471 
472  auto* engine = context->GetEngine();
473  auto* exec = static_cast<ScriptExecutor*>(engine->GetUserData());
474 
475  // Verify types //
476  const auto elementID = systemcomponents.GetElementTypeId();
477  const auto wantedID = AngelScriptTypeIDResolver<ScriptSystemUses>::Get(exec);
478 
479  if(elementID != wantedID) {
480 
481  context->SetException(("expected systemcomponents array to hold objects of type " +
482  std::to_string(wantedID) + " but it contains type " +
483  std::to_string(elementID))
484  .c_str());
485  return;
486  }
487 
488  // And then the harder to verify the one that can be anything //
489  asITypeInfo* givenComponentsType = engine->GetTypeInfoById(cachedtypeid);
490 
491  // This is always named array so this isn't needed
492  // asITypeInfo* baseArrayType = engine->GetTypeInfoByDecl("array<T>");
493  // const char* baseName = baseArrayType->GetName();
494  const char* baseName = "array";
495 
496  if(std::strcmp(givenComponentsType->GetName(), baseName) != 0) {
497 
498  context->SetException(
499  ("expected cachedcomponents to be an array type: " + std::string(baseName) +
500  " but it is type: " + std::string(givenComponentsType->GetName()))
501  .c_str());
502  return;
503  }
504 
505  // Needs to be a handle type //
506  if(!(cachedtypeid & asTYPEID_OBJHANDLE)) {
507 
508  context->SetException("expected cachedcomponents to be a handle to array type");
509  return;
510  }
511 
512  // Handle type so it is a double pointer //
513  CScriptArray* cached = *static_cast<CScriptArray**>(cachedcomponents);
514 
515  asITypeInfo* cacheclass = engine->GetTypeInfoById(cached->GetElementTypeId());
516 
517  if(!cacheclass) {
518 
519  context->SetException(("failed to get type inside cachedcomponents, id: " +
520  std::to_string(systemcomponents.GetElementTypeId()))
521  .c_str());
522  return;
523  }
524 
525  std::vector<std::tuple<void*, ObjectID, ComponentTypeInfo>> addedCpp;
526  std::vector<std::tuple<asIScriptObject*, ObjectID, ScriptComponentHolder*>> addedScript;
527  std::vector<std::tuple<void*, ObjectID>> removedCpp;
528  std::vector<std::tuple<asIScriptObject*, ObjectID>> removedScript;
529 
530  // Get all the added and removed at once //
531  // TODO: Might be more cache efficient to first get all added c++ and then all added script
532  // and then move on to the removed ones
533  for(asUINT i = 0; i < systemcomponents.GetSize(); ++i) {
534 
535  ScriptSystemUses* type = static_cast<ScriptSystemUses*>(systemcomponents.At(i));
536 
537  if(type->UsesName) {
538 
539  world->GetAddedForScriptDefined(type->Name, addedScript);
540  world->GetRemovedForScriptDefined(type->Name, removedScript);
541 
542  } else {
543 
544  world->GetAddedFor(static_cast<COMPONENT_TYPE>(type->Type), addedCpp);
545  world->GetRemovedFor(static_cast<COMPONENT_TYPE>(type->Type), removedCpp);
546  }
547  }
548 
549  // Only do more checks if something has changed //
550  if(!addedCpp.empty() || !addedScript.empty()) {
551 
552  // Handle added like in TupleCachedComponentCollectionHelper //
553 
554  // Find the first factory that has the right number of arguments //
555  asIScriptFunction* factoryFunc = nullptr;
556 
557  const asUINT factoryCount = cacheclass->GetFactoryCount();
558  const auto expectedParamCount = systemcomponents.GetSize() + 1;
559 
560  for(asUINT i = 0; i < factoryCount; ++i) {
561 
562  asIScriptFunction* currentToCheck = cacheclass->GetFactoryByIndex(i);
563 
564  if(currentToCheck->GetParamCount() == expectedParamCount) {
565  factoryFunc = currentToCheck;
566  break;
567  }
568  }
569 
570  if(!factoryFunc) {
571 
572  context->SetException(
573  ("type inside cachedcomponents has no suitable factory, expected one with " +
574  std::to_string(expectedParamCount) +
575  " parameters, type: " + std::string(cacheclass->GetName()))
576  .c_str());
577  return;
578  }
579 
580 
581  // For sanity reasons this is split into a helper which goes through all of the added
582  // vectors again trying to build an enity of the ID
583  for(const auto& tuple : addedCpp) {
584 
585  if(!TryToCreateNewCachedComponentsForEntity(std::get<1>(tuple), cached,
586  factoryFunc, addedCpp, addedScript, world, systemcomponents, context, exec))
587  return;
588  }
589 
590  for(const auto& tuple : addedScript) {
591 
592  // Skip if already checked //
593  bool inCpp = false;
594  for(const auto& alreadyDone : addedCpp) {
595 
596  if(std::get<1>(tuple) == std::get<1>(alreadyDone)) {
597 
598  inCpp = true;
599  break;
600  }
601  }
602 
603  if(inCpp)
604  continue;
605 
606  if(!TryToCreateNewCachedComponentsForEntity(std::get<1>(tuple), cached,
607  factoryFunc, addedCpp, addedScript, world, systemcomponents, context, exec))
608  return;
609  }
610  }
611 
612  if(!removedCpp.empty() || !removedScript.empty()) {
613  // And deleted like in any system that does CachedComponents.RemoveBasedOnKeyTupleList
614 
615  // Cheaper to query each object only once about their id //
616  for(asUINT i = 0; i < cached->GetSize();) {
617 
618  ObjectID currentID;
619  if(!GetCachedScriptObjectIDAtIndex(cached, i, context, exec, currentID))
620  return;
621 
622  bool remove = false;
623 
624  for(const auto& tuple : removedCpp) {
625 
626  if(std::get<1>(tuple) == currentID) {
627  remove = true;
628  break;
629  }
630  }
631 
632  if(!remove) {
633  for(const auto& tuple : removedScript) {
634 
635  if(std::get<1>(tuple) == currentID) {
636  remove = true;
637  break;
638  }
639  }
640  }
641 
642  if(!remove) {
643  ++i;
644  continue;
645  }
646 
647  // Remove it //
648  // We do a swap trick here //
649  // This should work even for size == 1
650  cached->SetValue(i, cached->At(cached->GetSize() - 1));
651  cached->RemoveLast();
652  }
653  }
654 }
virtual DLLEXPORT std::tuple< void *, ComponentTypeInfo, bool > GetComponentWithType(ObjectID id, COMPONENT_TYPE type)
Gets a component of type or returns nullptr.
Definition: GameWorld.cpp:1101
DLLEXPORT asIScriptObject * Find(ObjectID entity)
Finds a component for entity.
int32_t ObjectID
Definition: EntityCommon.h:11
ScriptRunResult< ReturnT > ExecuteCustomRun(const std::unique_ptr< CustomScriptRun > &run)
Ends a custom script run by actually executing the script and returning a value.
#define LOG_ERROR(x)
Definition: Define.h:92
static int Get(ScriptExecutor *resolver)
DLLEXPORT void Run(float elapsed)
DLLEXPORT void Init(GameWorld *world)
Handles ScriptModule creation and AngelScript code execution.
bool TryToCreateNewCachedComponentsForEntity(ObjectID newentity, CScriptArray *cached, asIScriptFunction *factoryfunc, std::vector< std::tuple< void *, ObjectID, ComponentTypeInfo >> &addedcpp, std::vector< std::tuple< asIScriptObject *, ObjectID, ScriptComponentHolder * >> &addedscript, GameWorld *world, CScriptArray &systemcomponents, asIScriptContext *context, ScriptExecutor *exec)
DLLEXPORT asIScriptObject * GetASImplementationObject()
Returns the ImplementationObject increasing refcount.
virtual DLLEXPORT bool GetAddedFor(COMPONENT_TYPE type, std::vector< std::tuple< void *, ObjectID, ComponentTypeInfo >> &result)
Gets a list of created components of type.
Definition: GameWorld.cpp:1132
DLLEXPORT ScriptSystemWrapper(const std::string &name, asIScriptObject *impl)
DLLEXPORT bool GetRemovedForScriptDefined(const std::string &name, std::vector< std::tuple< asIScriptObject *, ObjectID >> &result)
Variant of GetRemovedFor for script defined types.
Definition: GameWorld.cpp:1118
ComponentFindStatusForCache(asIScriptObject *as)
DLLEXPORT void ScriptSystemNodeHelper(GameWorld *world, void *cachedcomponents, int cachedtypeid, CScriptArray &systemcomponents)
Helper for script systems to call to properly handle added and removed nodes.
bool GetCachedScriptObjectIDAtIndex(CScriptArray *cached, asUINT index, asIScriptContext *context, ScriptExecutor *exec, ObjectID &id)
ComponentFindStatusForCache(void *cpp, const ComponentTypeInfo &info)
DLLEXPORT ScriptComponentHolder * GetScriptComponentHolder(const std::string &name)
Retrieves a script registered component type holder.
Definition: GameWorld.cpp:1710
#define DLLEXPORT
Definition: Include.h:84
DLLEXPORT std::unique_ptr< CustomScriptRun > PrepareCustomScriptRun(asIScriptFunction *func, ScriptRunningSetup extraoptions=ScriptRunningSetup())
Starts a script run that supports custom argument passing.
DLLEXPORT bool GetAddedForScriptDefined(const std::string &name, std::vector< std::tuple< asIScriptObject *, ObjectID, ScriptComponentHolder * >> &result)
Variant of GetAddedFor for script defined types.
Definition: GameWorld.cpp:1138
The access mask controls which registered functions and classes a script sees.
Definition: GameModule.h:12
virtual DLLEXPORT bool GetRemovedFor(COMPONENT_TYPE type, std::vector< std::tuple< void *, ObjectID >> &result)
Gets a list of destroyed components of type.
Definition: GameWorld.cpp:1112
Holds a single component type from c++ or from script, which a ScriptSystem uses.
bool PassParameterToCustomRun(std::unique_ptr< CustomScriptRun > &run, uint32_t value)
Represents a world that contains entities.
Definition: GameWorld.h:57