// // Urho3D Engine // Copyright (c) 2008-2011 Lasse Öörni // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #include "Precompiled.h" #include "Log.h" #include "PhysicsEvents.h" #include "PhysicsWorld.h" #include "RegisterArray.h" #include "ReplicationUtils.h" #include "ResourceCache.h" #include "Scene.h" #include "SceneEvents.h" #include "ScriptEngine.h" #include "ScriptFile.h" #include "ScriptInstance.h" #include "StringUtils.h" #include #include "DebugNew.h" static const std::string methodDeclarations[] = { "void start()", "void stop()", "void update(float)", "void postUpdate(float)", "void updateFixed(float)", "void postUpdateFixed(float)", "void save(Serializer&)", "void load(Deserializer&)", "void postLoad()", "void saveXML(XMLElement&)", "void loadXML(const XMLElement&)", "void writeNetUpdate(Serializer&, const NetUpdateInfo&)", "void readNetUpdate(Deserializer&, const NetUpdateInfo&)", "void postNetUpdate()", "void interpolate(bool)", "array getComponentRefs()", "array getResourceRefs()" }; std::map objectToInstance; ScriptInstance::ScriptInstance(ScriptEngine* scriptEngine, const std::string& name) : Component(name), mScriptEngine(scriptEngine), mScriptObject(0), mEnabled(true), mFixedUpdateFps(0), mFixedUpdateInterval(0.0f), mFixedUpdateTimer(0.0f), mFixedPostUpdateTimer(0.0f) { if (!mScriptEngine) EXCEPTION("Null script engine for ScriptInstance"); clearMethods(); } ScriptInstance::~ScriptInstance() { releaseObject(); } void ScriptInstance::save(Serializer& dest) { Component::save(dest); dest.writeBool(mEnabled); dest.writeStringHash(getResourceHash(mScriptFile)); dest.writeString(mClassName); dest.writeInt(mFixedUpdateFps); dest.writeFloat(mFixedUpdateTimer); dest.writeVLE(mDelayedMethodCalls.size()); for (unsigned i = 0; i < mDelayedMethodCalls.size(); ++i) { const DelayedMethodCall& call = mDelayedMethodCalls[i]; dest.writeFloat(call.mDelay); dest.writeString(call.mDeclaration); dest.writeVLE(call.mParameters.size()); for (unsigned j = 0; j < call.mParameters.size(); ++j) dest.writeVariant(call.mParameters[j]); } // Save script's data into a separate buffer for safety static VectorBuffer scriptBuffer; scriptBuffer.clear(); if (mMethods[METHOD_SAVE]) { VariantVector parameters; parameters.push_back(Variant((void*)static_cast(&scriptBuffer))); mScriptFile->execute(mScriptObject, mMethods[METHOD_SAVE], parameters); } dest.writeVLE(scriptBuffer.getSize()); dest.write(scriptBuffer.getData(), scriptBuffer.getSize()); } void ScriptInstance::load(Deserializer& source, ResourceCache* cache) { Component::load(source, cache); mEnabled = source.readBool(); StringHash scriptFile = source.readStringHash(); std::string className = source.readString(); setScriptClass(cache->getResource(scriptFile), className); mFixedUpdateFps = source.readInt(); mFixedUpdateTimer = mFixedPostUpdateTimer = source.readFloat(); mFixedUpdateInterval = mFixedUpdateFps ? (1.0f / mFixedUpdateFps) : 0.0f; clearDelayedExecute(); unsigned numCalls = source.readVLE(); for (unsigned i = 0; i < numCalls; ++i) { DelayedMethodCall call; call.mDelay = source.readFloat(); call.mDeclaration = source.readString(); unsigned numParameters = source.readVLE(); for (unsigned j = 0; j < numParameters; ++j) call.mParameters.push_back(source.readVariant()); mDelayedMethodCalls.push_back(call); } static VectorBuffer scriptBuffer; unsigned scriptDataSize = source.readVLE(); scriptBuffer.setData(source, scriptDataSize); if (mMethods[METHOD_LOAD]) { VariantVector parameters; parameters.push_back(Variant((void*)static_cast(&scriptBuffer))); mScriptFile->execute(mScriptObject, mMethods[METHOD_LOAD], parameters); } } void ScriptInstance::postLoad(ResourceCache* cache) { if (mMethods[METHOD_POSTLOAD]) mScriptFile->execute(mScriptObject, mMethods[METHOD_POSTLOAD]); } void ScriptInstance::saveXML(XMLElement& dest) { Component::saveXML(dest); XMLElement scriptElem = dest.createChildElement("script"); scriptElem.setString("name", getResourceName(mScriptFile)); scriptElem.setString("class", mClassName); scriptElem.setBool("enabled", mEnabled); scriptElem.setInt("fps", mFixedUpdateFps); scriptElem.setFloat("timeacc", mFixedUpdateTimer); for (unsigned i = 0; i < mDelayedMethodCalls.size(); ++i) { const DelayedMethodCall& call = mDelayedMethodCalls[i]; XMLElement callElem = dest.createChildElement("call"); callElem.setFloat("delay", call.mDelay); callElem.setString("method", call.mDeclaration); for (unsigned j = 0; j < call.mParameters.size(); ++j) { XMLElement paramElem = callElem.createChildElement("parameter"); paramElem.setVariant(call.mParameters[j]); } } if (mMethods[METHOD_SAVEXML]) { XMLElement dataElem = dest.createChildElement("data"); VariantVector parameters; parameters.push_back(Variant((void*)&dataElem)); mScriptFile->execute(mScriptObject, mMethods[METHOD_SAVEXML], parameters); } } void ScriptInstance::loadXML(const XMLElement& source, ResourceCache* cache) { Component::loadXML(source, cache); XMLElement scriptElem = source.getChildElement("script"); mEnabled = scriptElem.getBool("enabled"); setScriptClass(cache->getResource(scriptElem.getString("name")), scriptElem.getString("class")); mFixedUpdateFps = scriptElem.getInt("fps"); mFixedUpdateTimer = mFixedPostUpdateTimer = scriptElem.getFloat("timeacc"); mFixedUpdateInterval = mFixedUpdateFps ? (1.0f / mFixedUpdateFps) : 0.0f; clearDelayedExecute(); XMLElement callElem = source.getChildElement("call"); while (callElem) { DelayedMethodCall call; call.mDelay = callElem.getFloat("delay"); call.mDeclaration = callElem.getString("method"); XMLElement paramElem = callElem.getChildElement("parameter"); while (paramElem) { call.mParameters.push_back(paramElem.getVariant()); paramElem = paramElem.getNextElement("parameter"); } mDelayedMethodCalls.push_back(call); callElem = callElem.getNextElement("call"); } if (mMethods[METHOD_LOADXML]) { XMLElement dataElem = source.getChildElement("data"); if (dataElem) { VariantVector parameters; parameters.push_back(Variant((void*)&dataElem)); mScriptFile->execute(mScriptObject, mMethods[METHOD_LOADXML], parameters); } } } bool ScriptInstance::writeNetUpdate(Serializer& dest, Serializer& destRevision, Deserializer& baseRevision, const NetUpdateInfo& info) { unsigned char bits = 0; StringHash scriptFileHash = getResourceHash(mScriptFile); checkBool(mEnabled, true, baseRevision, bits, 1); checkStringHash(scriptFileHash, StringHash(), baseRevision, bits, 2); checkString(mClassName, std::string(), baseRevision, bits, 2); checkInt(mFixedUpdateFps, 0, baseRevision, bits, 4); checkFloat(mFixedUpdateTimer, 0.0f, baseRevision, bits, 8); // Save script's data into a separate buffer for safety static VectorBuffer scriptBuffer; scriptBuffer.clear(); // Write update only if enabled if ((mEnabled) && (mMethods[METHOD_WRITENETUPDATE])) { VariantVector parameters; parameters.push_back(Variant((void*)static_cast(&scriptBuffer))); parameters.push_back(Variant((void*)&info)); mScriptFile->execute(mScriptObject, mMethods[METHOD_WRITENETUPDATE], parameters); } // Compare buffer to previous revision if available unsigned scriptDataSize = scriptBuffer.getSize(); if (scriptDataSize) checkBuffer(scriptBuffer, baseRevision, bits, 16); dest.writeUByte(bits); writeBoolDelta(mEnabled, dest, destRevision, bits & 1); writeStringHashDelta(scriptFileHash, dest, destRevision, bits & 2); writeStringDelta(mClassName, dest, destRevision, bits & 2); writeIntDelta(mFixedUpdateFps, dest, destRevision, bits & 4); writeFloatDelta(mFixedUpdateTimer, dest, destRevision, bits & 8); writeBufferDelta(scriptBuffer, dest, destRevision, bits & 16); return bits != 0; } void ScriptInstance::readNetUpdate(Deserializer& source, ResourceCache* cache, const NetUpdateInfo& info) { unsigned char bits = source.readUByte(); if (bits & 1) mEnabled = source.readBool(); if (bits & 2) { StringHash scriptFile = source.readStringHash(); std::string className = source.readString(); setScriptClass(cache->getResource(scriptFile), className); } if (bits & 4) { mFixedUpdateFps = source.readInt(); mFixedUpdateInterval = mFixedUpdateFps ? (1.0f / mFixedUpdateFps) : 0.0f; } if (bits & 8) mFixedUpdateTimer = mFixedPostUpdateTimer = source.readFloat(); if (bits & 16) { static VectorBuffer scriptBuffer; unsigned scriptDataSize = source.readVLE(); scriptBuffer.setData(source, scriptDataSize); if (mMethods[METHOD_READNETUPDATE]) { VariantVector parameters; parameters.push_back(Variant((void*)static_cast(&scriptBuffer))); parameters.push_back(Variant((void*)&info)); mScriptFile->execute(mScriptObject, mMethods[METHOD_READNETUPDATE], parameters); } } } void ScriptInstance::postNetUpdate(ResourceCache* cache) { if (mMethods[METHOD_POSTNETUPDATE]) mScriptFile->execute(mScriptObject, mMethods[METHOD_POSTNETUPDATE]); } void ScriptInstance::interpolate(bool snapToEnd) { if ((mEnabled) && (mMethods[METHOD_INTERPOLATE])) { VariantVector parameters; parameters.push_back(Variant(snapToEnd)); mScriptFile->execute(mScriptObject, mMethods[METHOD_INTERPOLATE], parameters); } } void ScriptInstance::getComponentRefs(std::vector& dest) { if ((mEnabled) && (mMethods[METHOD_GETCOMPONENTREFS])) { asIScriptContext* context = mScriptEngine->getScriptFileContext(getScriptNestingLevel()); if (!context) return; mScriptFile->execute(mScriptObject, mMethods[METHOD_GETCOMPONENTREFS], VariantVector(), false); CScriptArray* arr = static_cast(context->GetAddressOfReturnValue()); if (arr) { for (unsigned i = 0; i < arr->GetSize(); ++i) dest.push_back(*(static_cast(arr->At(i)))); } context->Unprepare(); } } void ScriptInstance::getResourceRefs(std::vector& dest) { if (mMethods[METHOD_GETRESOURCEREFS]) { asIScriptContext* context = mScriptEngine->getScriptFileContext(getScriptNestingLevel()); if (!context) return; mScriptFile->execute(mScriptObject, mMethods[METHOD_GETRESOURCEREFS], VariantVector(), false); CScriptArray* arr = static_cast(context->GetAddressOfReturnValue()); if (arr) { for (unsigned i = 0; i < arr->GetSize(); ++i) dest.push_back(*(static_cast(arr->At(i)))); } context->Unprepare(); } } bool ScriptInstance::setScriptClass(ScriptFile* scriptFile, const std::string& className) { if ((scriptFile == mScriptFile) && (className == mClassName)) return true; releaseObject(); mScriptFile = scriptFile; mClassName = className; if ((!mScriptFile) && (mClassName.empty())) return true; return createObject(); } void ScriptInstance::setEnabled(bool enable) { mEnabled = enable; } void ScriptInstance::setFixedUpdateFps(int fps) { mFixedUpdateFps = max(fps, 0); mFixedUpdateInterval = mFixedUpdateFps ? (1.0f / mFixedUpdateFps) : 0.0f; mFixedUpdateTimer = 0.0f; mFixedPostUpdateTimer = 0.0f; } bool ScriptInstance::execute(const std::string& declaration, const VariantVector& parameters) { if (!mScriptObject) return false; asIScriptFunction* method = mScriptFile->getMethod(mScriptObject, declaration); return mScriptFile->execute(mScriptObject, method, parameters); } bool ScriptInstance::execute(asIScriptFunction* method, const VariantVector& parameters) { if ((!method) || (!mScriptObject)) return false; return mScriptFile->execute(mScriptObject, method, parameters); } void ScriptInstance::delayedExecute(float delay, const std::string& declaration, const VariantVector& parameters) { if (!mScriptObject) return; DelayedMethodCall call; call.mDelay = max(delay, 0.0f); call.mDeclaration = declaration; call.mParameters = parameters; mDelayedMethodCalls.push_back(call); // Make sure we are registered to the scene update event, because delayed calls are executed there if ((!mMethods[METHOD_UPDATE]) && (!hasSubscribedToEvent(EVENT_SCENEUPDATE))) subscribeToEvent(EVENT_SCENEUPDATE, EVENT_HANDLER(ScriptInstance, handleSceneUpdate)); } void ScriptInstance::clearDelayedExecute() { mDelayedMethodCalls.clear(); } void ScriptInstance::addEventHandler(StringHash eventType, const std::string& handlerName) { if (!mScriptObject) return; std::string declaration = "void " + handlerName + "(StringHash, VariantMap&)"; asIScriptFunction* method = mScriptFile->getMethod(mScriptObject, declaration); if (!method) { declaration = "void " + handlerName + "()"; method = mScriptFile->getMethod(mScriptObject, declaration); if (!method) { LOGERROR("Event handler method " + handlerName + " not found in " + mScriptFile->getName()); return; } } subscribeToEvent(eventType, EVENT_HANDLER_USERDATA(ScriptInstance, handleScriptEvent, (void*)method)); } void ScriptInstance::addEventHandler(EventListener* sender, StringHash eventType, const std::string& handlerName) { if (!mScriptObject) return; if (!sender) { LOGERROR("Null event sender for event " + toString(eventType) + ", handler " + handlerName); return; } std::string declaration = "void " + handlerName + "(StringHash, VariantMap&)"; asIScriptFunction* method = mScriptFile->getMethod(mScriptObject, declaration); if (!method) { declaration = "void " + handlerName + "()"; method = mScriptFile->getMethod(mScriptObject, declaration); if (!method) { LOGERROR("Event handler method " + handlerName + " not found in " + mScriptFile->getName()); return; } } subscribeToEvent(sender, eventType, EVENT_HANDLER_USERDATA(ScriptInstance, handleScriptEvent, (void*)method)); } bool ScriptInstance::createObject() { if (!mScriptFile) { LOGERROR("Null script file for ScriptInstance"); return false; } if (mClassName.empty()) { LOGERROR("Empty script class name"); return false; } mScriptObject = mScriptFile->createObject(mClassName); if (mScriptObject) { mScriptFile->addScriptInstance(this); objectToInstance[(void*)mScriptObject] = this; getSupportedMethods(); if (mMethods[METHOD_START]) mScriptFile->execute(mScriptObject, mMethods[METHOD_START]); return true; } else { LOGERROR("Failed to create object of class " + mClassName + " from " + mScriptFile->getName()); return false; } } void ScriptInstance::releaseObject() { if (mScriptObject) { if (mMethods[METHOD_STOP]) mScriptFile->execute(mScriptObject, mMethods[METHOD_STOP]); removeAllEventHandlers(); unsubscribeFromAllEvents(); clearMethods(); clearDelayedExecute(); mScriptObject->Release(); mScriptObject = 0; objectToInstance.erase((void*)mScriptObject); mScriptFile->removeScriptInstance(this); } } void ScriptInstance::clearMethods() { for (unsigned i = 0; i < MAX_SCRIPT_METHODS; ++i) mMethods[i] = 0; mDelayedMethodCalls.clear(); } void ScriptInstance::getSupportedMethods() { for (unsigned i = 0; i < MAX_SCRIPT_METHODS; ++i) mMethods[i] = mScriptFile->getMethod(mScriptObject, methodDeclarations[i]); // Subscribe to the update events as supported if (mMethods[METHOD_UPDATE]) subscribeToEvent(EVENT_SCENEUPDATE, EVENT_HANDLER(ScriptInstance, handleSceneUpdate)); if (mMethods[METHOD_POSTUPDATE]) subscribeToEvent(EVENT_SCENEPOSTUPDATE, EVENT_HANDLER(ScriptInstance, handleScenePostUpdate)); if (mMethods[METHOD_UPDATEFIXED]) subscribeToEvent(EVENT_PHYSICSPRESTEP, EVENT_HANDLER(ScriptInstance, handlePhysicsPreStep)); if (mMethods[METHOD_POSTUPDATEFIXED]) subscribeToEvent(EVENT_PHYSICSPOSTSTEP, EVENT_HANDLER(ScriptInstance, handlePhysicsPostStep)); } void ScriptInstance::handleSceneUpdate(StringHash eventType, VariantMap& eventData) { if ((!mEnabled) || (!mScriptObject)) return; using namespace SceneUpdate; float timeStep = eventData[P_TIMESTEP].getFloat(); // Check that the scene matches Scene* scene = mEntity ? mEntity->getScene() : 0; if (eventData[P_SCENE].getPtr() == (void*)scene) { // Execute delayed method calls for (std::vector::iterator i = mDelayedMethodCalls.begin(); i != mDelayedMethodCalls.end();) { i->mDelay -= timeStep; if (i->mDelay <= 0.0f) { execute(i->mDeclaration, i->mParameters); i = mDelayedMethodCalls.erase(i); } else ++i; } if (mMethods[METHOD_UPDATE]) { VariantVector parameters; parameters.push_back(timeStep); mScriptFile->execute(mScriptObject, mMethods[METHOD_UPDATE], parameters); } } } void ScriptInstance::handleScenePostUpdate(StringHash eventType, VariantMap& eventData) { if ((!mEnabled) || (!mScriptObject)) return; using namespace ScenePostUpdate; // Check that the scene matches Scene* scene = mEntity ? mEntity->getScene() : 0; if (eventData[P_SCENE].getPtr() == (void*)scene) { VariantVector parameters; parameters.push_back(eventData[P_TIMESTEP]); mScriptFile->execute(mScriptObject, mMethods[METHOD_POSTUPDATE], parameters); } } void ScriptInstance::handlePhysicsPreStep(StringHash eventType, VariantMap& eventData) { if ((!mEnabled) || (!mScriptObject)) return; using namespace PhysicsPreStep; // Check that the scene matches Scene* scene = mEntity ? mEntity->getScene() : 0; if (eventData[P_SCENE].getPtr() == (void*)scene) { if (!mFixedUpdateFps) { VariantVector parameters; parameters.push_back(eventData[P_TIMESTEP]); mScriptFile->execute(mScriptObject, mMethods[METHOD_UPDATEFIXED], parameters); } else { float timeStep = eventData[P_TIMESTEP].getFloat(); mFixedUpdateTimer += timeStep; if (mFixedUpdateTimer >= mFixedUpdateInterval) { mFixedUpdateTimer = fmodf(mFixedUpdateTimer, mFixedUpdateInterval); VariantVector parameters; parameters.push_back(mFixedUpdateInterval); mScriptFile->execute(mScriptObject, mMethods[METHOD_UPDATEFIXED], parameters); } } } } void ScriptInstance::handlePhysicsPostStep(StringHash eventType, VariantMap& eventData) { if ((!mEnabled) || (!mScriptObject)) return; using namespace PhysicsPostStep; // Check that the scene matches Scene* scene = mEntity ? mEntity->getScene() : 0; if (eventData[P_SCENE].getPtr() == (void*)scene) { if (!mFixedUpdateFps) { VariantVector parameters; parameters.push_back(eventData[P_TIMESTEP]); mScriptFile->execute(mScriptObject, mMethods[METHOD_UPDATEFIXED], parameters); } else { float timeStep = eventData[P_TIMESTEP].getFloat(); mFixedPostUpdateTimer += timeStep; if (mFixedPostUpdateTimer >= mFixedUpdateInterval) { mFixedPostUpdateTimer = fmodf(mFixedPostUpdateTimer, mFixedUpdateInterval); VariantVector parameters; parameters.push_back(mFixedUpdateInterval); mScriptFile->execute(mScriptObject, mMethods[METHOD_UPDATEFIXED], parameters); } } } } void ScriptInstance::handleScriptEvent(StringHash eventType, VariantMap& eventData) { if ((!mEnabled) || (!mScriptFile) || (!mScriptObject)) return; asIScriptFunction* method = static_cast(getInvoker()->getUserData()); VariantVector parameters; if (method->GetParamCount() > 0) { parameters.push_back(Variant((void*)&eventType)); parameters.push_back(Variant((void*)&eventData)); } mScriptFile->execute(mScriptObject, method, parameters); } ScriptInstance* getScriptContextInstance() { void* object = asGetActiveContext()->GetThisPointer(); std::map::const_iterator i = objectToInstance.find(object); if (i != objectToInstance.end()) return i->second; else return 0; } Entity* getScriptContextEntity() { ScriptInstance* instance = getScriptContextInstance(); return instance ? instance->getEntity() : 0; } ScriptEventListener* getScriptContextEventListener() { // First try to get the script instance. If not found, get the script file for procedural event handling ScriptInstance* instance = getScriptContextInstance(); if (instance) return instance; ScriptFile* file = getScriptContextFile(); return file; }