Files
starruler-linux/source/game/scripts/script_components.cpp
T
2018-07-17 14:15:37 +02:00

1522 lines
38 KiB
C++

#include "script_components.h"
#include "main/references.h"
#include "main/logging.h"
#include "compat/misc.h"
#include "network.h"
#include "network/network_manager.h"
#include "str_util.h"
#include "empire.h"
#include "util/save_file.h"
#include "obj/lock.h"
#include "obj/object.h"
#include "scene/scripted_node.h"
#include "general_states.h"
#include "context_cache.h"
#include <unordered_map>
#include <assert.h>
extern void writeObject(net::Message& msg, Object* obj, bool includeType = true);
extern Object* readObject(net::Message& msg, bool create, int knownTypeID = -1);
//#define LOG_SLOW_ASYNC_CALLS
//#define PROFILE_ASYNC_CALLS
#ifdef PROFILE_ASYNC_CALLS
threads::atomic_int callCount[4];
void logAsyncLoad() {
int counts[] = { callCount[0].get_basic(), callCount[1].get_basic(), callCount[2].get_basic(), callCount[3].get_basic() };
float total = float(counts[0] + counts[1] + counts[2] + counts[3]) / 100.f;
print(" 0us- 10us: %d (%.0f%%)\n 10us-100us: %d (%.0f%%)\n100us- 1ms: %d (%.0f%%)\n 1ms- inf: %d (%.0f%%)",
counts[0], float(counts[0]) / total,
counts[1], float(counts[1]) / total,
counts[2], float(counts[2]) / total,
counts[3], float(counts[3]) / total );
}
#else
void logAsyncLoad() {
print("Async profiling disabled.");
}
#endif
namespace scripts {
void logException();
extern Player SERVER_PLAYER;
std::vector<Component*> components;
std::unordered_map<std::string, Component*> componentNames;
std::unordered_map<const StateValueDefinition*, std::pair<Component*, bool>> componentDefs;
Threaded(bool) RELOCK_SAFE_METHODS = false;
Threaded(bool) PREVENT_RELOCKING = false;
unsigned getComponentCount() {
return (unsigned)components.size();
}
Component* getComponent(unsigned index) {
if(index >= components.size())
return 0;
return components[index];
}
Component* getComponent(const std::string& name) {
auto it = componentNames.find(name);
if(it != componentNames.end())
return it->second;
return 0;
}
Component* getComponent(const StateValueDefinition* def, bool* optional) {
auto it = componentDefs.find(def);
if(it != componentDefs.end()) {
if(optional)
*optional = it->second.second;
return it->second.first;
}
if(optional)
*optional = false;
return 0;
}
Component::~Component() {
for(auto it = methods.begin(), end = methods.end(); it != end; ++it)
delete *it;
}
void clearComponents() {
for(auto it = components.begin(), end = components.end(); it != end; ++it)
delete *it;
components.clear();
componentNames.clear();
componentDefs.clear();
}
void MethodFlags::parse(std::string& flags) {
while(true) {
if(flags.compare(0, 11, "restricted ") == 0) {
restricted = true;
flags = flags.substr(11);
}
else if(flags.compare(0, 7, "remote ") == 0) {
local = false;
server = false;
shadow = false;
flags = flags.substr(7);
}
else if(flags.compare(0, 6, "local ") == 0) {
local = true;
server = false;
shadow = false;
flags = flags.substr(6);
}
else if(flags.compare(0, 7, "server ") == 0) {
server = true;
local = true;
shadow = false;
flags = flags.substr(7);
}
else if(flags.compare(0, 7, "shadow ") == 0) {
server = false;
shadow = true;
local = true;
flags = flags.substr(7);
}
else if(flags.compare(0, 6, "async ") == 0) {
async = true;
flags = flags.substr(6);
}
else if(flags.compare(0, 5, "safe ") == 0) {
safe = true;
flags = flags.substr(5);
}
else if(flags.compare(0, 10, "relocking ") == 0) {
relocking = true;
flags = flags.substr(10);
}
else if(flags.compare(0, 8, "visible ") == 0) {
visible = true;
flags = flags.substr(8);
}
else if(flags.compare(0, 10, "invisible ") == 0) {
visible = false;
flags = flags.substr(10);
}
else {
break;
}
}
}
void loadComponents(const std::string& filename) {
std::ifstream file(filename);
if(!file.is_open()) {
error("Could not open '%s'", filename.c_str());
return;
}
skipBOM(file);
Component* comp = 0;
auto finishDefinition = [&]() {
if(!comp)
return;
componentNames[comp->name] = comp;
components.push_back(comp);
comp = 0;
};
bool inType = false;
char name[80], value[80];
char bracket;
MethodFlags flags;
#ifdef DOCUMENT_API
WrappedMethod* prevMethod = 0;
#endif
while(true) {
std::string line;
std::getline(file, line);
if(file.fail())
break;
std::string parse = line.substr(0, line.find("//"));
if(parse.find_first_not_of(" \t\n\r") == std::string::npos)
continue;
if(!inType) {
finishDefinition();
int args = sscanf(parse.c_str(), " %79s : %79s {", name, value);
if(args != 2) {
error("Unrecognized line '%s'", line.c_str());
continue;
}
std::string typeName = name;
std::string containing = "Object";
auto dot = typeName.find('.');
if(dot != std::string::npos) {
containing = typeName.substr(0, dot);
typeName = typeName.substr(dot + 1);
}
if(typeName.empty() || containing.empty()) {
error("Component name specifier '%s' invalid.", name);
continue;
}
if(componentNames.find(typeName) != componentNames.end()) {
error("Duplicate type '%s'", name);
continue;
}
comp = new Component();
comp->id = components.size();
comp->name = typeName;
comp->typeDecl = value;
comp->containing = containing;
if(containing == "Empire")
comp->containingType = GT_Empire_Ref;
else
comp->containingType = GT_Object_Ref;
inType = true;
}
else if(comp) {
int args = sscanf(parse.c_str(), " %c", &bracket);
if(args == 1 && bracket == '}') {
inType = false;
continue;
}
parse = trim(parse);
if(!parse.empty()) {
if(parse[parse.size() - 1] == ':') {
parse[parse.size() - 1] = ' ';
flags.reset();
flags.parse(parse);
continue;
}
if(parse[0] == '@') {
#ifdef DOCUMENT_API
if(prevMethod) {
if(parse.compare(0, 5, "@doc ") == 0) {
if(prevMethod->doc_desc.empty()) {
prevMethod->doc_desc = parse.substr(5);
}
else {
prevMethod->doc_desc += '\n';
prevMethod->doc_desc += parse.substr(5);
}
}
else if(parse.compare(0, 5, "@arg ") == 0) {
prevMethod->doc_args.push_back(parse.substr(5));
}
else if(parse.compare(0, 8, "@return ") == 0) {
prevMethod->doc_return = parse.substr(8);
}
}
#endif
continue;
}
}
//Parse declaration
GenericCallDesc desc;
MethodFlags curFlags = flags;
curFlags.parse(parse);
if(curFlags.server || curFlags.shadow)
desc.parse(parse, true, true);
else
desc.parse(parse, true, false);
if(!curFlags.local && desc.returnType.type) {
error("Error: '%s': non-local function cannot return a value.", parse.c_str());
continue;
}
else if(desc.returnType == GT_Custom_Handle) {
error("Error: '%s': script types cannot be return values.", parse.c_str());
continue;
}
//Create method desc
WrappedMethod* m = new WrappedMethod();
m->fullDeclaration = parse;
m->index = comp->methods.size();
m->compId = comp->id;
m->local = curFlags.local;
m->server = curFlags.server;
m->shadow = curFlags.shadow;
m->wrapped = desc;
m->restricted = curFlags.restricted;
m->async = curFlags.async;
m->safe = curFlags.safe;
m->relocking = curFlags.relocking;
m->isComponentMethod = true;
if(m->wrapped.returnsArray) {
m->wrapped.returnType.type = GT_Custom_Handle;
m->wrapped.returnType.customName = "DataList";
}
m->desc.append(desc);
m->desc.name = desc.name;
m->desc.constFunction = false;
m->desc.returnType = desc.returnType;
m->desc.returnsArray = m->wrapped.returnsArray;
comp->methods.push_back(m);
#ifdef DOCUMENT_API
prevMethod = m;
#endif
}
else {
if(parse.find('}') != std::string::npos)
inType = false;
}
}
finishDefinition();
}
const std::string nullstr("null");
void addComponentStateValueTypes() {
//Setup value types
foreach(it, components) {
Component& comp = **it;
std::string name = "Component_";
name += comp.name;
name += "@";
stateValueTypes[comp.name.c_str()].setup(
sizeof(asIScriptObject*), name,
//Always create a new component
[&comp](void* m, void* s) {
asIScriptObject** dest = (asIScriptObject**)m;
if(comp.type) {
asIScriptFunction* func = comp.type->GetFactoryByIndex(0);
Call cl = devices.scripts.server->call(func);
cl.call(*dest);
if(*dest)
(*dest)->AddRef();
}
else {
*dest = 0;
}
},
//No initialization
nullptr,
//Release reference on destruct
[](void* mem) {
asIScriptObject* obj = *(asIScriptObject**)mem;
if(obj)
obj->Release();
},
nullptr, nullptr, nullptr,
[](void* mem) {
asIScriptObject* obj = *(asIScriptObject**)mem;
if(obj) {
obj->Release();
*(void**)mem = nullptr;
}
}
);
std::string optname = comp.name + "@";
stateValueTypes[optname.c_str()].setup(
sizeof(asIScriptObject*), name,
//Always create a new component if not
//null-initialized
[&comp](void* m, void* s) {
asIScriptObject** dest = (asIScriptObject**)m;
if(s) {
asIScriptFunction* func = comp.type->GetFactoryByIndex(0);
Call cl = devices.scripts.server->call(func);
cl.call(*dest);
if(*dest)
(*dest)->AddRef();
}
else {
*dest = 0;
}
},
//Check for initialization
[](const std::string& str) -> void* {
if(streq_nocase(str, nullstr))
return 0;
return (void*)1;
},
//Release reference on destruct
[](void* mem) {
asIScriptObject* obj = *(asIScriptObject**)mem;
if(obj)
obj->Release();
},
nullptr, nullptr, nullptr,
[](void* mem) {
asIScriptObject* obj = *(asIScriptObject**)mem;
if(obj) {
obj->Release();
*(void**)mem = nullptr;
}
}
);
}
//Get pointers
foreach(it, components) {
Component& comp = **it;
comp.def = getStateValueType(comp.name);
comp.optDef = getStateValueType(comp.name+"@");
componentDefs[comp.def] = std::pair<Component*,bool>(&comp, false);
componentDefs[comp.optDef] = std::pair<Component*,bool>(&comp, true);
}
}
class AsyncScriptCall : public ObjectMessage {
WrappedMethod* method;
GenericCallData args;
size_t compOffset;
bool fromGUI;
GenericValue* retValue;
public:
#ifdef LOG_SLOW_ASYNC_CALLS
double* retTime;
#endif
AsyncScriptCall(WrappedMethod* Method, GenericCallData& arguments, size_t offset,
bool FromGUI, GenericValue* pReturn = 0
#ifdef LOG_SLOW_ASYNC_CALLS
, double* RetTime = 0
#endif
)
: ObjectMessage((Object*)arguments.object), method(Method), args(arguments),
compOffset(offset), fromGUI(FromGUI), retValue(pReturn)
{
object->grab();
#ifdef LOG_SLOW_ASYNC_CALLS
retTime = RetTime;
#endif
}
void process() {
asIScriptObject* script;
if(method->isComponentMethod)
script = *(asIScriptObject**)((size_t)object + compOffset);
else
script = object->script;
if(!script || (fromGUI && method->restricted && !devices.network->getCurrentPlayer().controls(object->owner))) {
if(retValue) {
retValue->asyncReturned = true;
#ifdef LOG_SLOW_ASYNC_CALLS
if(retTime) *retTime = devices.driver->getAccurateTime();
#endif
}
return;
}
if(!object->isValid())
return;
Call cl = devices.scripts.server->call(method->func);
cl.setObject(script);
if(method->passPlayer) {
if(fromGUI)
cl.push((void*)&devices.network->getCurrentPlayer());
else
cl.push((void*)&SERVER_PLAYER);
}
if(method->passContaining)
cl.push((void*)object);
args.pushTo(cl);
//Save relocking values
bool prevPrevent = PREVENT_RELOCKING;
bool prevSafe = RELOCK_SAFE_METHODS;
PREVENT_RELOCKING = !method->relocking;
RELOCK_SAFE_METHODS = false;
if(retValue) {
GenericValue ret = method->desc.call(cl);
auto* type = method->desc.returnType.type;
if(type)
type->copy(*retValue, ret);
else
*retValue = ret;
#ifdef LOG_SLOW_ASYNC_CALLS
if(retTime) *retTime = devices.driver->getAccurateTime();
#endif
retValue->asyncReturned = true;
}
else {
method->desc.call(cl);
}
//Restore relocking values
PREVENT_RELOCKING = prevPrevent;
RELOCK_SAFE_METHODS = prevSafe;
}
~AsyncScriptCall() {
object->drop();
}
};
/* Components registered to objects */
static GenericValue wrapperFunc(void* arg, GenericCallData& args) {
GenericValue retVal;
WrappedMethod* method = (WrappedMethod*)arg;
Object* obj = (Object*)args.object;
asIScriptObject* script;
size_t offset;
if(!obj->isValid())
return retVal;
if(method->isComponentMethod) {
offset = obj->type->componentOffsets[method->compId];
if(offset == 0) {
throwException(format("Object of type '$1' does not have component '$2'.",
obj->type->name, getComponent(method->compId)->name).c_str());
return retVal;
}
script = *(asIScriptObject**)((size_t)obj + offset);
}
else {
script = obj->script;
offset = 0;
}
if(!script)
return retVal;
if(!method->local && devices.network->isClient) {
net::Message msg(MT_Object_Component_Call, net::MF_Managed);
writeObject(msg, obj);
msg.writeBit(method->isComponentMethod);
msg.writeAlign();
if(method->isComponentMethod)
msg.writeSmall(method->compId);
msg.writeSmall(method->index);
args.write(msg);
devices.network->send(msg);
obj->focus();
}
if(method->func) {
bool isGUI = scripts::getActiveManager() != devices.scripts.server;
bool hasReturn = method->desc.returnsArray || method->desc.returnType.type;
LockGroup* activeGroup = getActiveLockGroup();
//Check relocking requirement
if(PREVENT_RELOCKING) {
if(method->relocking && (!method->async || hasReturn)) {
throwException("Cannot call relocking method from non-relocking outer function.");
return retVal;
}
if(hasReturn && (!method->safe || RELOCK_SAFE_METHODS) && activeGroup && obj != getActiveObject()) {
throwException("Cannot call methods with return data on different objects unless the outer function is declared relocking.");
return retVal;
}
}
bool initialized = obj->isInitialized();
if((obj->isLocked() || (method->safe && !RELOCK_SAFE_METHODS)) && !method->async && initialized) {
Call cl = devices.scripts.server->call(method->func);
cl.setObject(script);
if(method->passPlayer) {
if(isGUI)
cl.push((void*)&devices.network->getCurrentPlayer());
else
cl.push((void*)&SERVER_PLAYER);
}
if(method->passContaining)
cl.push((void*)obj);
args.pushTo(cl);
//Check restriction
if(method->restricted && isGUI) {
if(!devices.network->getCurrentPlayer().controls(obj->owner)) {
info(format("Cannot call method '$1' on unowned objects.", method->wrapped.name).c_str());
return retVal;
}
}
//Save relocking values
bool prevPrevent = PREVENT_RELOCKING;
bool prevSafe = RELOCK_SAFE_METHODS;
Object* prevObj = getActiveObject();
setActiveObject(obj);
PREVENT_RELOCKING = !method->relocking;
RELOCK_SAFE_METHODS = false;
//Run method
retVal = method->desc.call(cl);
//Restore relocking values
setActiveObject(prevObj);
PREVENT_RELOCKING = prevPrevent;
RELOCK_SAFE_METHODS = prevSafe;
if(cl.status != asSUCCESS) {
throwException("Wrapped script call unsuccessful.");
return retVal;
}
}
else {
//Prepare an asynchronous call
//TODO: Avoid destructing empty GenericCallData
if(hasReturn) {
if(initialized) {
#if defined(LOG_SLOW_ASYNC_CALLS) || defined(PROFILE_ASYNC_CALLS)
double start = devices.driver->getAccurateTime();
double retTime;
bool wasFast = true;
#endif
obj->lockGroup->addMessage(
new AsyncScriptCall(method, args, offset, isGUI, &retVal
#ifdef LOG_SLOW_ASYNC_CALLS
, &retTime
#endif
)
);
//Attempt to process the message immediately where possible (typically the GUI, which needs the fast response)
const unsigned ASYNC_SPIN_COUNT = 24;
const unsigned FAST_SPIN_COUNT = 2500;
if(!activeGroup) {
unsigned spins = ASYNC_SPIN_COUNT;
while(!retVal.asyncReturned) {
if(!obj->lockGroup->hasLock())
tickLockMessages(obj->lockGroup);
else
tickRandomMessages(1);
if(--spins == 0) {
threads::sleep(0);
if(!obj->isValid())
return retVal;
spins = ASYNC_SPIN_COUNT;
continue;
}
}
}
else {
//Fast path: Often, the ret value will have been produced by the end of the loop
unsigned spins = 0;
while(!retVal.asyncReturned) {
++spins;
if(spins == FAST_SPIN_COUNT)
break;
}
//Slow path: Perhaps it can't be resolved, or will take a lot longer than we'd like
// This can involve a lot of other messages taking a very long time to return
while(!retVal.asyncReturned) {
#ifdef LOG_SLOW_ASYNC_CALLS
wasFast = false;
#endif
if(!activeGroup->messages.empty())
activeGroup->processMessages(1);
else
threads::sleep(0);
if(!obj->isValid())
return retVal;
}
}
#if defined(LOG_SLOW_ASYNC_CALLS) || defined(PROFILE_ASYNC_CALLS)
double end = devices.driver->getAccurateTime();
double dur = end - start;
#ifdef PROFILE_ASYNC_CALLS
if(dur < 1.e-4)
if(dur < 1.e-5)
callCount[0]++;
else
callCount[1]++;
else
if(dur < 1.e-3)
callCount[2]++;
else
callCount[3]++;
#endif
#ifdef LOG_SLOW_ASYNC_CALLS
if(dur > 1.0e-3) {
error("Long wait (%.1fms, wasted %.1fms%s) for %s", (end-start) * 1.0e3, (end-retTime) * 1.0e3, (activeGroup && wasFast) ? " fast" : "", method->func->GetName());
auto* ctx = asGetActiveContext();
if(ctx)
error(getStackTrace(ctx, false));
}
#endif
#endif
}
}
else if(initialized) {
obj->lockGroup->addMessage(new AsyncScriptCall(method, args, offset, isGUI));
}
else {
obj->queueDeferredMessage(new AsyncScriptCall(method, args, offset, isGUI));
}
}
}
else if(method->shadow || !devices.network->isClient) {
throwException("Unbound function called.");
return retVal;
}
return retVal;
}
class ObjectWaitMessage : public ObjectMessage {
public:
volatile bool* done;
ObjectWaitMessage(Object* obj, volatile bool* done)
: ObjectMessage(obj), done(done) {
object->grab();
}
void process() {
*done = true;
}
~ObjectWaitMessage() {
object->drop();
*done = true;
}
};
void objectWait(Object* obj) {
//Check relocking requirement
if(PREVENT_RELOCKING) {
throwException("Cannot call relocking Object.wait() method from non-relocking outer function.");
return;
}
if(!obj->isInitialized())
return;
volatile bool done = false;
auto* msg = new ObjectWaitMessage(obj, &done);
obj->lockGroup->addMessage(msg);
LockGroup* activeGroup = getActiveLockGroup();
while(!done) {
threads::sleep(0);
if(activeGroup)
activeGroup->processMessages(4);
else
tickRandomMessages(10);
}
}
bool getSafeCallWait() {
return RELOCK_SAFE_METHODS;
}
void setSafeCallWait(bool wait) {
RELOCK_SAFE_METHODS = wait;
}
void handleObjectComponentMessage(Player* from, net::Message& msg) {
Object* obj = readObject(msg, false);
if(obj) {
bool componentCall = msg.readBit();
unsigned compId, index;
msg.readAlign();
if(componentCall)
compId = msg.readSmall();
index = msg.readSmall();
WrappedMethod* method;
asIScriptObject* script;
if(componentCall) {
Component* comp = getComponent(compId);
if(!comp || index >= comp->methods.size())
return;
if(comp->containingType != GT_Object_Ref)
return;
method = comp->methods[index];
if(!method->func)
return;
size_t offset = obj->type->componentOffsets[method->compId];
script = *(asIScriptObject**)((size_t)obj + offset);
}
else {
if(index >= obj->type->states->methods.size())
return;
method = obj->type->states->methods[index].wrapped;
if(!method || !method->func)
return;
script = obj->script;
}
if(!script)
return;
GenericCallData args(method->wrapped);
if(!args.read(msg)) {
obj->drop();
return;
}
Call cl = devices.scripts.server->call(method->func);
cl.setObject(script);
if(method->passPlayer)
cl.push((void*)from);
if(method->passContaining)
cl.push((void*)obj);
args.pushTo(cl);
{
//Check restriction
ObjectLock lock(obj);
if(!method->restricted || from->controls(obj->owner))
cl.call();
}
obj->drop();
}
}
static void hasComponent(asIScriptGeneric* f) {
bool has = false;
Component* comp = (Component*)f->GetFunction()->GetUserData();
Object* obj = (Object*)f->GetObject();
size_t offset = obj->type->componentOffsets[comp->id];
if(offset != 0) {
asIScriptObject* scr = *(asIScriptObject**)((size_t)obj + offset);
if(scr != 0) {
has = true;
}
}
f->SetReturnByte(has);
}
static void getComponentPtr(asIScriptGeneric* f) {
asIScriptObject* addr = 0;
Component* comp = (Component*)f->GetFunction()->GetUserData();
Object* obj = (Object*)f->GetObject();
size_t offset = obj->type->componentOffsets[comp->id];
if(offset != 0) {
addr = *(asIScriptObject**)((size_t)obj + offset);
if(addr)
addr->AddRef();
}
f->SetReturnAddress((void*)addr);
}
static void getKnownComponentPtr(asIScriptGeneric* f) {
asIScriptObject* addr = 0;
size_t offset = (size_t)f->GetFunction()->GetUserData();
Object* obj = (Object*)f->GetObject();
if(offset != 0) {
addr = *(asIScriptObject**)((size_t)obj + offset);
if(addr)
addr->AddRef();
}
f->SetReturnAddress((void*)addr);
}
static void activateComponent(asIScriptGeneric* f) {
Component* comp = (Component*)f->GetFunction()->GetUserData();
Object* obj = (Object*)f->GetObject();
size_t offset = obj->type->componentOffsets[comp->id];
if(offset != 0) {
asIScriptObject** addr = (asIScriptObject**)((size_t)obj + offset);
if(*addr)
return;
asIScriptFunction* func = comp->type->GetFactoryByIndex(0);
Call cl = devices.scripts.server->call(func);
cl.call(*addr);
if(*addr)
(*addr)->AddRef();
}
}
static void deactivateComponent(asIScriptGeneric* f) {
Component* comp = (Component*)f->GetFunction()->GetUserData();
Object* obj = (Object*)f->GetObject();
size_t offset = obj->type->componentOffsets[comp->id];
if(offset != 0) {
asIScriptObject** addr = (asIScriptObject**)((size_t)obj + offset);
if(*addr == 0)
return;
(*addr)->Release();
*addr = 0;
}
}
void RegisterObjectComponentWrappers(ClassBind& obj, bool server, ScriptObjectType* type) {
if(type) {
auto* states = type->states;
if(states) {
foreach(it, states->methods) {
WrappedMethod* method = it->wrapped;
if(!method)
continue;
if(server || (!method->server && !method->shadow))
bindGeneric(obj, method->wrapped, wrapperFunc, (void*)method, true);
}
}
}
//Register wrapper class for each component correctly
foreach(it, components) {
Component& comp = **it;
//Skip over things that are not in objects
if(comp.containingType != GT_Object_Ref)
continue;
//Skip over things not in this object type
if(type != nullptr && type->componentOffsets[comp.id] == 0)
continue;
//Add interface to the object
foreach(m, comp.methods) {
WrappedMethod* method = *m;
if(server || (!method->server && !method->shadow)) {
#ifdef DOCUMENT_API
int fid = bindGeneric(obj, method->wrapped, wrapperFunc, (void*)method, true);
auto* doc = new Documentor(fid, method->fullDeclaration);
doc->documented = !method->doc_desc.empty() || !method->doc_return.empty() || !method->doc_args.empty();
doc->funcDoc = method->doc_desc;
doc->retDoc = method->doc_return;
doc->argDoc = method->doc_args;
#else
bindGeneric(obj, method->wrapped, wrapperFunc, (void*)method, true);
#endif
}
}
if(type) {
//Check whether a component exists
if(type->optionalComponents[comp.id]) {
obj.addGenericMethod(format("bool get_has$1() const", comp.name).c_str(), asFUNCTION(hasComponent), &comp)
doc("Returns true if the object has the associated component.", "");
obj.addGenericMethod(format("void activate$1()", comp.name).c_str(), asFUNCTION(activateComponent), &comp)
doc("Activates an optional component if not already activated.");
obj.addGenericMethod(format("void deactivate$1()", comp.name).c_str(), asFUNCTION(deactivateComponent), &comp)
doc("Deactivates an optional component.");
}
//Get the component pointer
obj.addGenericMethod(format("Component_$1@ get_$1()", comp.name, comp.name).c_str(), asFUNCTION(getKnownComponentPtr), (void*)(size_t)type->componentOffsets[comp.id])
doc("Returns the component handle for this type of component.", "");
}
else {
//Check whether a component exists
obj.addGenericMethod(format("bool get_has$1() const", comp.name).c_str(), asFUNCTION(hasComponent), &comp)
doc("Returns true if the object has the associated component.", "");
//Get the component pointer
obj.addGenericMethod(format("Component_$1@ get_$1()", comp.name, comp.name).c_str(), asFUNCTION(getComponentPtr), &comp)
doc("Returns the component handle for this type of component.", "Can be null if no component of this type exists for this object.");
}
}
}
class AsyncEmpireScriptCall : public EmpireMessage {
WrappedMethod* method;
GenericCallData args;
size_t compOffset;
bool fromGUI;
GenericValue* retValue;
public:
AsyncEmpireScriptCall(WrappedMethod* Method, GenericCallData& arguments, size_t offset,
bool FromGUI, GenericValue* pReturn = 0)
: method(Method), args(arguments),
compOffset(offset), fromGUI(FromGUI), retValue(pReturn)
{
}
void process(Empire* emp) override {
asIScriptObject* script = *(asIScriptObject**)((size_t)emp + compOffset);
if(!script || (fromGUI && method->restricted && !devices.network->getCurrentPlayer().controls(emp))) {
if(script) //Attempted to call restricted method
info( format("Cannot call method '$1' on other empires.", method->wrapped.name).c_str() );
if(retValue)
retValue->asyncReturned = true;
return;
}
Call cl = devices.scripts.server->call(method->func);
cl.setObject(script);
if(method->passPlayer) {
if(fromGUI)
cl.push((void*)&devices.network->getCurrentPlayer());
else
cl.push((void*)&SERVER_PLAYER);
}
if(method->passContaining)
cl.push((void*)emp);
args.pushTo(cl);
//Save relocking values
bool prevPrevent = PREVENT_RELOCKING;
bool prevSafe = RELOCK_SAFE_METHODS;
Object* prevObj = getActiveObject();
PREVENT_RELOCKING = !method->relocking;
RELOCK_SAFE_METHODS = false;
if(retValue) {
GenericValue ret = method->desc.call(cl);
auto* type = method->desc.returnType.type;
if(type)
type->copy(*retValue, ret);
else
*retValue = ret;
retValue->asyncReturned = true;
}
else {
method->desc.call(cl);
}
//Restore relocking values
setActiveObject(prevObj);
PREVENT_RELOCKING = prevPrevent;
RELOCK_SAFE_METHODS = prevSafe;
}
};
/* Components registered to other empires */
static std::vector<size_t> empComponentOffsets;
static GenericValue emp_wrapperFunc(void* arg, GenericCallData& args) {
GenericValue retVal;
WrappedMethod* method = (WrappedMethod*)arg;
Empire* emp = (Empire*)args.object;
size_t offset = empComponentOffsets[method->compId];
if(offset == 0) {
throwException(format("Empire does not have component '$2'.",
getComponent(method->compId)->name).c_str());
return retVal;
}
asIScriptObject* comp = *(asIScriptObject**)((size_t)emp + offset);
if(!comp)
return retVal;
//Check restriction
bool isGUI = scripts::getActiveManager() != devices.scripts.server;
if(method->restricted) {
if(!devices.network->getCurrentPlayer().controls(emp) && isGUI) {
info(
format("Cannot call method '$1' on other empires.",
method->wrapped.name).c_str());
return retVal;
}
}
if(!method->local && devices.network->isClient) {
net::Message msg(MT_Empire_Component_Call, net::MF_Managed);
msg << emp->id;
msg << method->compId;
msg << method->index;
args.write(msg);
devices.network->send(msg);
}
if(method->func) {
bool hasReturn = method->desc.returnsArray || method->desc.returnType.type;
auto* activeGroup = getActiveLockGroup();
if(!method->async || !activeGroup) {
Call cl = devices.scripts.server->call(method->func);
cl.setObject(comp);
if(method->passPlayer) {
if(isGUI)
cl.push((void*)&devices.network->getCurrentPlayer());
else
cl.push((void*)&SERVER_PLAYER);
}
if(method->passContaining)
cl.push((void*)emp);
args.pushTo(cl);
if(hasReturn) {
GenericValue ret = method->desc.call(cl);
auto* type = method->desc.returnType.type;
if(type)
type->copy(retVal, ret);
else
retVal = ret;
}
else {
method->desc.call(cl);
}
}
else {
if(PREVENT_RELOCKING) {
if(hasReturn || method->relocking) {
throwException("Cannot call relocking method from non-relocking outer function.");
return retVal;
}
}
emp->queueMessage(new AsyncEmpireScriptCall(method, args, offset, isGUI, hasReturn ? &retVal : nullptr));
//Wait for async data return
if(hasReturn) {
auto* activeGroup = getActiveLockGroup();
while(!retVal.asyncReturned) {
threads::sleep(0);
if(activeGroup)
activeGroup->processMessages(4);
}
}
}
}
else if(method->shadow || !devices.network->isClient) {
throwException("Unbound function called.");
return retVal;
}
return retVal;
}
void handleEmpireComponentMessage(Player* from, net::Message& msg) {
unsigned char id;
msg >> id;
Empire* emp = Empire::getEmpireByID(id);
if(emp) {
unsigned compId, index;
msg >> compId;
msg >> index;
Component* comp = getComponent(compId);
if(index >= comp->methods.size())
return;
if(comp->containingType != GT_Empire_Ref)
return;
WrappedMethod* method = comp->methods[index];
if(!method->func)
return;
//Check restriction
if(method->restricted && !from->controls(emp))
return;
size_t offset = empComponentOffsets[method->compId];
asIScriptObject* script = *(asIScriptObject**)((size_t)emp + offset);
GenericCallData args(method->wrapped);
if(!args.read(msg))
return;
Call cl = devices.scripts.server->call(method->func);
cl.setObject(script);
if(method->passPlayer)
cl.push((void*)from);
if(method->passContaining)
cl.push((void*)emp);
args.pushTo(cl);
cl.call();
}
}
static void emp_hasComponent(asIScriptGeneric* f) {
bool has = false;
Component* comp = (Component*)f->GetFunction()->GetUserData();
Empire* emp = (Empire*)f->GetObject();
size_t offset = empComponentOffsets[comp->id];
if(offset != 0) {
asIScriptObject* scr = *(asIScriptObject**)((size_t)emp + offset);
if(scr != 0) {
has = true;
}
}
f->SetReturnByte(has);
}
static void emp_getComponentPtr(asIScriptGeneric* f) {
asIScriptObject* addr = 0;
Component* comp = (Component*)f->GetFunction()->GetUserData();
Empire* emp = (Empire*)f->GetObject();
size_t offset = empComponentOffsets[comp->id];
if(offset != 0) {
addr = *(asIScriptObject**)((size_t)emp + offset);
if(addr)
addr->AddRef();
}
f->SetReturnAddress((void*)addr);
}
//TODO: Optimize this knowing the Empire at bind-time
void BindEmpireComponentOffsets() {
const StateDefinition& def = *Empire::getEmpireStates();
unsigned bytes = sizeof(Empire);
StateDefinition::align(bytes);
//Zero all empire component offsets
unsigned compCnt = getComponentCount();
empComponentOffsets.resize(compCnt);
for(unsigned i = 0; i < compCnt; ++i)
empComponentOffsets[i] = 0;
//Find the component offsets for the empire
for(auto var = def.types.begin(), endvar = def.types.end(); var != endvar; ++var) {
auto& type = *var->def;
Component* comp = getComponent(&type);
if(comp) {
if(empComponentOffsets[comp->id] != 0)
error("ERROR: Empire has two components of type '%s'.", comp->name.c_str());
else
empComponentOffsets[comp->id] = bytes;
}
bytes += type.size;
}
}
void RegisterEmpireComponentWrappers(ClassBind& emp, bool server) {
//Register wrapper class for each component correctly
foreach(it, components) {
Component& comp = **it;
//Skip over things that are not in empires
if(comp.containingType != GT_Empire_Ref)
continue;
//Add interface to the empire
foreach(m, comp.methods) {
WrappedMethod* method = *m;
if(server || (!method->server && !method->shadow)) {
#ifdef DOCUMENT_API
int fid = bindGeneric(emp, method->wrapped, emp_wrapperFunc, (void*)method, true);
auto* doc = new Documentor(fid, method->fullDeclaration);
doc->documented = !method->doc_desc.empty() || !method->doc_return.empty() || !method->doc_args.empty();
doc->funcDoc = method->doc_desc;
doc->retDoc = method->doc_return;
doc->argDoc = method->doc_args;
#else
bindGeneric(emp, method->wrapped, emp_wrapperFunc, (void*)method, true);
#endif
}
}
//Check whether a component exists
int fid = getEngine()->RegisterObjectMethod(emp.name.c_str(),
format("bool get_has$1() const", comp.name).c_str(),
asFUNCTION(emp_hasComponent), asCALL_GENERIC);
if(fid > 0)
getEngine()->GetFunctionById(fid)->SetUserData(&comp);
else
assert(false);
//Get the component pointer
fid = getEngine()->RegisterObjectMethod(emp.name.c_str(),
format("Component_$1@ get_$1()", comp.name, comp.name).c_str(),
asFUNCTION(emp_getComponentPtr), asCALL_GENERIC);
if(fid > 0)
getEngine()->GetFunctionById(fid)->SetUserData(&comp);
else
assert(false);
}
}
/* Node Method Wrappers */
class AsyncNodeCall : public scene::NodeEvent {
WrappedMethod* method;
GenericCallData args;
public:
AsyncNodeCall(scene::ScriptedNode* node, WrappedMethod* Method, GenericCallData& Args) : NodeEvent(node), method(Method), args(Args) {
}
void process() {
//Nodes that are being destroyed will have a null script object
scene::ScriptedNode* scriptedNode = (scene::ScriptedNode*)node;
if(scriptedNode->scriptObject) {
Call cl = devices.scripts.client->call(method->func);
cl.setObject(scriptedNode->scriptObject);
if(method->passContaining)
cl.push((void*)scriptedNode);
args.pushTo(cl);
method->desc.call(cl);
}
}
};
static GenericValue nodeWrapperFunc(void* arg, GenericCallData& args) {
GenericValue retVal;
WrappedMethod* method = (WrappedMethod*)arg;
scene::ScriptedNode* node = (scene::ScriptedNode*)args.object;
if(method->func) {
//Prepare an asynchronous call
//TODO: Avoid destructing empty GenericCallData
scene::queueNodeEvent(new AsyncNodeCall(node, method, args));
}
else {
throwException("Unbound function called.");
return retVal;
}
return retVal;
}
void RegisterNodeMethodWrappers(ClassBind& cls, bool server, scene::scriptNodeType* type) {
foreach(m, type->methods) {
WrappedMethod* method = *m;
bindGeneric(cls, method->wrapped, nodeWrapperFunc, (void*)method, false);
}
}
/* Component interface registration */
void RegisterComponentInterfaces(bool server) {
//Register interface for each component correctly
foreach(it, components) {
Component& comp = **it;
std::string name = "Component_";
name += comp.name;
InterfaceBind intf(name.c_str());
}
}
/* Retrieve the function pointers for the interfaces */
void bindComponentClasses() {
//Find classes to instantiate for each component
foreach(it, components) {
Component& comp = **it;
std::vector<std::string> args;
split(comp.typeDecl, args, "::");
if(args.size() != 2) {
error("Invalid script class specifier: '%s'", comp.typeDecl.c_str());
continue;
}
comp.type = devices.scripts.server->getClass(args[0].c_str(), args[1].c_str());
if(!comp.type) {
error("Script class not found: '%s'", comp.typeDecl.c_str());
continue;
}
comp.save = comp.type->GetMethodByDecl("void save(SaveFile&)");
std::string loadFactoryDecl = comp.name + "@ " + comp.name + "(SaveFile&)";
comp.load = comp.type->GetFactoryByDecl(loadFactoryDecl.c_str());
foreach(m, comp.methods) {
WrappedMethod* method = *m;
if(!devices.network->isClient) {
if(method->shadow)
continue;
}
if(!method->func)
method->origDesc = method->desc;
GenericCallDesc desc = method->origDesc;
//Try to find the const version first
if(method->wrapped.constFunction) {
desc.constFunction = true;
method->func = comp.type->GetMethodByDecl(desc.declaration().c_str());
if(method->func) {
continue;
}
//Prepend the containing object to see if we need it
desc.prepend(ArgumentDesc(comp.containingType, method->wrapped.constFunction));
method->func = comp.type->GetMethodByDecl(desc.declaration().c_str());
if(method->func) {
method->desc = desc;
method->passContaining = true;
continue;
}
//Prepend the player to see if we need it
desc.prepend(GT_Player_Ref);
method->func = comp.type->GetMethodByDecl(desc.declaration().c_str());
if(method->func) {
method->desc = desc;
method->passContaining = true;
method->passPlayer = true;
continue;
}
desc = method->origDesc;
desc.constFunction = false;
method->passContaining = false;
method->passPlayer = false;
}
method->func = comp.type->GetMethodByDecl(desc.declaration().c_str());
if(method->func)
continue;
//Prepend the containing object to see if we need it
desc.prepend(ArgumentDesc(comp.containingType, method->wrapped.constFunction));
method->func = comp.type->GetMethodByDecl(desc.declaration().c_str());
if(method->func) {
method->desc = desc;
method->passContaining = true;
continue;
}
//Prepend the player to see if we need it
desc.prepend(GT_Player_Ref);
method->func = comp.type->GetMethodByDecl(desc.declaration().c_str());
if(method->func) {
method->desc = desc;
method->passContaining = true;
method->passPlayer = true;
continue;
}
//Give an error if nothing resolved
if(!devices.network->isClient || method->shadow || (method->local && !method->server))
error("Method not found: %s -> %s",
comp.typeDecl.c_str(),
method->desc.declaration().c_str());
}
}
}
};