/* Copyright 2011 ROBLOX Corporation, All Rights Reserved */ #include "stdafx.h" #include "V8DataModel/Remote.h" #include "Network/Players.h" #include "FastLog.h" #include "util/RobloxGoogleAnalytics.h" DYNAMIC_FASTINTVARIABLE(RemoteDelayedQueueLimit, 256) namespace { static void sendRemoteFunctionStatsOnServer(const char* function) { RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_GAME, "RemoteFunction", function); } static void sendRemoteEventStatsOnServer(const char* function) { RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_GAME, "RemoteEvent", function); } } // namespace namespace RBX { REFLECTION_BEGIN(); const char* const sRemoteFunction = "RemoteFunction"; static Reflection::BoundYieldFuncDesc(shared_ptr)> func_InvokeServer(&RemoteFunction::invokeServer, "InvokeServer", "arguments", Security::None); static Reflection::BoundYieldFuncDesc(shared_ptr, shared_ptr)> func_InvokeClient(&RemoteFunction::invokeClient, "InvokeClient", "player", "arguments", Security::None); static Reflection::BoundAsyncCallbackDesc(shared_ptr, shared_ptr)> callback_OnServerInvoke("OnServerInvoke", &RemoteFunction::onServerInvoke, "player", "arguments", &RemoteFunction::onServerInvokeChanged); static Reflection::BoundAsyncCallbackDesc(shared_ptr)> callback_OnClientInvoke("OnClientInvoke", &RemoteFunction::onClientInvoke, "arguments", &RemoteFunction::onClientInvokeChanged); static Reflection::RemoteEventDesc, shared_ptr)> event_RemoteOnInvokeServer(&RemoteFunction::remoteOnInvokeServer, "RemoteOnInvokeServer", "id", "player", "arguments", Security::None, Reflection::RemoteEventCommon::REPLICATE_ONLY, Reflection::RemoteEventCommon::CLIENT_SERVER); static Reflection::RemoteEventDesc)> event_RemoteOnInvokeClient(&RemoteFunction::remoteOnInvokeClient, "RemoteOnInvokeClient", "id", "arguments", Security::None, Reflection::RemoteEventCommon::REPLICATE_ONLY, Reflection::RemoteEventCommon::CLIENT_SERVER); static Reflection::RemoteEventDesc)> event_RemoteOnInvokeSuccess(&RemoteFunction::remoteOnInvokeSuccess, "RemoteOnInvokeSuccess", "id", "arguments", Security::None, Reflection::RemoteEventCommon::REPLICATE_ONLY, Reflection::RemoteEventCommon::CLIENT_SERVER); static Reflection::RemoteEventDesc event_RemoteOnInvokeError(&RemoteFunction::remoteOnInvokeError, "RemoteOnInvokeError", "id", "arguments", Security::None, Reflection::RemoteEventCommon::REPLICATE_ONLY, Reflection::RemoteEventCommon::CLIENT_SERVER); const char* const sRemoteEvent = "RemoteEvent"; static Reflection::BoundFuncDesc)> func_FireServer(&RemoteEvent::fireServer, "FireServer", "arguments", Security::None); static Reflection::BoundFuncDesc, shared_ptr)> func_FireClient(&RemoteEvent::fireClient, "FireClient", "player", "arguments", Security::None); static Reflection::BoundFuncDesc)> func_FireAllClients(&RemoteEvent::fireAllClients, "FireAllClients", "arguments", Security::None); static Reflection::RemoteEventDesc, shared_ptr), LatchedSignal, shared_ptr)>, LatchedSignal, shared_ptr)>* (RemoteEvent::*)(bool) > event_OnServerEvent(&RemoteEvent::getOrCreateOnServerEvent, "OnServerEvent", "player", "arguments", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER); static Reflection::RemoteEventDesc), LatchedSignal)>, LatchedSignal)>* (RemoteEvent::*)(bool) > event_OnClientEvent(&RemoteEvent::getOrCreateOnClientEvent, "OnClientEvent", "arguments", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER); REFLECTION_END(); bool isPlayerValid(Instance* instance, const SystemAddress& address) { Network::Player* player = Instance::fastDynamicCast(instance); return player && player->getRemoteAddressAsRbxAddress() == address; } DelayedInvocationQueue::DelayedInvocationQueue(size_t limit) : limit(limit) { } bool DelayedInvocationQueue::push(const boost::function& item) { if (items.size() >= limit) return false; items.push_back(item); return true; } void DelayedInvocationQueue::process() { std::vector > temp; items.swap(temp); for (size_t i = 0; i < temp.size(); ++i) { try { temp[i](); } catch (const RBX::base_exception& e) { RBX::StandardOut::singleton()->printf(RBX::MESSAGE_OUTPUT, "DelayedInvocationQueue::process encountered an error %s", e.what()); RBXCRASH(); } } } RemoteFunction::RemoteFunction() : DescribedCreatable("RemoteFunction") , delayedInvocations(DFInt::RemoteDelayedQueueLimit) , lastRemoteInvocationId(0) { remoteOnInvokeSuccess.connect(boost::bind(&RemoteFunction::localSuccess, this, _1, _2)); remoteOnInvokeError.connect(boost::bind(&RemoteFunction::localError, this, _1, _2)); } void RemoteFunction::invokeServer(shared_ptr arguments, boost::function)> resumeFunction, boost::function errorFunction) { if (Network::Players::serverIsPresent(this)) { throw std::runtime_error("InvokeServer can only be called from the client"); } else if (Network::Players::clientIsPresent(this)) { Instance* player = Network::Players::findLocalPlayer(this); int id = createRemoteInvocation(weak_ptr(), resumeFunction, errorFunction); // send a remote event Reflection::EventArguments args(3); args[0] = id; args[1] = shared_from(player); args[2] = arguments; raiseEventInvocation(event_RemoteOnInvokeServer, args, NULL); } else { // play solo; fire the event locally to simulate client-server replication Instance* player = Network::Players::findLocalPlayer(this); localInvokeServer(shared_from(player), arguments, resumeFunction, errorFunction); } } void RemoteFunction::invokeClient(shared_ptr player, shared_ptr arguments, boost::function)> resumeFunction, boost::function errorFunction) { if (Network::Players::clientIsPresent(this)) { throw std::runtime_error("InvokeClient can only be called from the server"); } else if (Network::Players::serverIsPresent(this)) { Network::Player* p = Instance::fastDynamicCast(player.get()); if (!p) throw std::runtime_error("InvokeClient: player argument must be a Player object"); const RBX::SystemAddress& target = p->getRemoteAddressAsRbxAddress(); int id = createRemoteInvocation(player, resumeFunction, errorFunction); Reflection::EventArguments args(2); args[0] = id; args[1] = arguments; raiseEventInvocation(event_RemoteOnInvokeClient, args, &target); { static boost::once_flag flag = BOOST_ONCE_INIT; boost::call_once(flag, boost::bind(&sendRemoteFunctionStatsOnServer, "invokeClient")); } } else { // play solo; fire the event locally to simulate client-server replication localInvokeClient(arguments, resumeFunction, errorFunction); } } void RemoteFunction::onServerInvokeChanged(const ServerInvokeCallback& oldValue) { if (Network::Players::clientIsPresent(this)) { onServerInvoke.clear(); throw std::runtime_error("OnServerInvoke can only be implemented on the server"); } processDelayedInvocations(); } void RemoteFunction::onClientInvokeChanged(const ClientInvokeCallback& oldValue) { if (Network::Players::serverIsPresent(this)) { onClientInvoke.clear(); throw std::runtime_error("OnClientInvoke can only be implemented on the client"); } processDelayedInvocations(); } void RemoteFunction::processDelayedInvocations() { delayedInvocations.process(); } bool RemoteFunction::askSetParent( const Instance* instance ) const { return true; } void RemoteFunction::onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider) { Instance::onServiceProvider(oldProvider, newProvider); playerRemovingConnection.disconnect(); if (Network::Players* players = ServiceProvider::find(newProvider)) { playerRemovingConnection = players->playerRemovingSignal.connect(boost::bind(&RemoteFunction::onPlayerRemoving, this, _1)); } } bool RemoteFunction::consumeRemoteInvocation(int id, RemoteInvocation& result) { std::map::iterator it = remoteInvocations.find(id); if (it == remoteInvocations.end()) return false; result = it->second; remoteInvocations.erase(it); return true; } int RemoteFunction::createRemoteInvocation(const weak_ptr& player, const boost::function)>& resumeFunction, const boost::function& errorFunction) { // Generate (hopefully, unique) invocation id in [1..INT_MAX] if (lastRemoteInvocationId == INT_MAX) lastRemoteInvocationId = 0; int id = ++lastRemoteInvocationId; // Insert an invocation continuation RBXASSERT(remoteInvocations.count(id) == 0); RemoteInvocation invocation = {player, resumeFunction, errorFunction}; remoteInvocations[id] = invocation; return id; } void RemoteFunction::processRemoteEvent(const Reflection::EventDescriptor& descriptor, const Reflection::EventArguments& args, const SystemAddress& source) { if (&descriptor == &event_RemoteOnInvokeServer) { RBXASSERT(args.size() == 3); int id = args[0].cast(); shared_ptr player = args[1].cast >(); shared_ptr arguments = args[2].cast >(); // Validate player object; ignore invalid calls for security reasons if (isPlayerValid(player.get(), source)) { localInvokeServer(player, arguments, boost::bind(&RemoteFunction::remoteSuccess, this, source, id, _1), boost::bind(&RemoteFunction::remoteError, this, source, id, _1)); } else { RBX::StandardOut::singleton()->printf(RBX::MESSAGE_SENSITIVE, "RemoteFunction::processRemoteEvent: ignore a remote call from %x:%d", source.getAddress(), source.getPort()); } } else if (&descriptor == &event_RemoteOnInvokeClient) { RBXASSERT(args.size() == 2); int id = args[0].cast(); shared_ptr arguments = args[1].cast >(); localInvokeClient(arguments, boost::bind(&RemoteFunction::remoteSuccess, this, source, id, _1), boost::bind(&RemoteFunction::remoteError, this, source, id, _1)); } else { EventSource::processRemoteEvent(descriptor, args, source); } } void RemoteFunction::localInvokeServer(shared_ptr player, shared_ptr arguments, boost::function)> resumeFunction, boost::function errorFunction) { if (onServerInvoke.empty()) { if (!delayedInvocations.push(boost::bind(&RemoteFunction::localInvokeServer, this, player, arguments, resumeFunction, errorFunction))) errorFunction("Remote function invocation queue exhausted for " + getFullName() + "; did you forget to implement OnServerInvoke?"); return; } { static boost::once_flag flag = BOOST_ONCE_INIT; boost::call_once(flag, boost::bind(&sendRemoteFunctionStatsOnServer, "invokeServer")); } onServerInvoke(player, arguments, resumeFunction, errorFunction); } void RemoteFunction::localInvokeClient(shared_ptr arguments, boost::function)> resumeFunction, boost::function errorFunction) { if (onClientInvoke.empty()) { if (!delayedInvocations.push(boost::bind(&RemoteFunction::localInvokeClient, this, arguments, resumeFunction, errorFunction))) errorFunction("Remote function invocation queue exhausted for " + getFullName() + "; did you forget to implement OnClientInvoke?"); return; } onClientInvoke(arguments, resumeFunction, errorFunction); } void RemoteFunction::remoteSuccess(SystemAddress address, int id, shared_ptr result) { Reflection::EventArguments args(2); args[0] = id; args[1] = result; raiseEventInvocation(event_RemoteOnInvokeSuccess, args, &address); } void RemoteFunction::remoteError(SystemAddress address, int id, std::string error) { Reflection::EventArguments args(2); args[0] = id; args[1] = error; raiseEventInvocation(event_RemoteOnInvokeError, args, &address); } void RemoteFunction::localSuccess(int id, shared_ptr arguments) { RemoteInvocation invocation; if (consumeRemoteInvocation(id, invocation)) { invocation.resumeFunction(arguments); } else { RBXASSERT(false); } } void RemoteFunction::localError(int id, std::string error) { RemoteInvocation invocation; if (consumeRemoteInvocation(id, invocation)) { invocation.errorFunction(error); } else { RBXASSERT(false); } } void RemoteFunction::onPlayerRemoving(shared_ptr player) { weak_ptr weakPlayer = player; std::vector invocations; for (std::map::iterator it = remoteInvocations.begin(); it != remoteInvocations.end(); ++it) if (weak_equal(it->second.player, weakPlayer)) invocations.push_back(it->first); std::string message = "Player " + player->getName() + " disconnected during remote call to " + getFullName(); for (size_t i = 0; i < invocations.size(); ++i) { RemoteInvocation invocation; consumeRemoteInvocation(invocations[i], invocation); invocation.errorFunction(message); } } RemoteEvent::RemoteEvent() : DescribedCreatable("RemoteEvent") , onServerEvent(this, "OnServerEvent", DFInt::RemoteDelayedQueueLimit) , onClientEvent(this, "OnClientEvent", DFInt::RemoteDelayedQueueLimit) { } void RemoteEvent::fireServer(shared_ptr arguments) { if (Network::Players::serverIsPresent(this)) { throw std::runtime_error("FireServer can only be called from the client"); } else if (Network::Players::clientIsPresent(this)) { Instance* player = Network::Players::findLocalPlayer(this); // remote call Reflection::EventArguments args(2); args[0] = shared_from(player); args[1] = arguments; raiseEventInvocation(event_OnServerEvent, args, NULL); } else { // play solo; fire the event locally to simulate client-server replication Instance* player = Network::Players::findLocalPlayer(this); onServerEvent(shared_from(player), arguments); } } void RemoteEvent::fireClient(shared_ptr player, shared_ptr arguments) { if (Network::Players::clientIsPresent(this)) { throw std::runtime_error("FireClient can only be called from the server"); } else if (Network::Players::serverIsPresent(this)) { Network::Player* p = Instance::fastDynamicCast(player.get()); if (!p) throw std::runtime_error("FireClient: player argument must be a Player object"); const RBX::SystemAddress& target = p->getRemoteAddressAsRbxAddress(); // remote call Reflection::EventArguments args(1); args[0] = arguments; raiseEventInvocation(event_OnClientEvent, args, &target); { static boost::once_flag flag = BOOST_ONCE_INIT; boost::call_once(flag, boost::bind(&sendRemoteEventStatsOnServer, "fireClient")); } } else { // play solo; fire the event locally to simulate client-server replication onClientEvent(arguments); } } void RemoteEvent::fireAllClients(shared_ptr arguments) { if (Network::Players::clientIsPresent(this)) { throw std::runtime_error("FireAllClients can only be called from the server"); } else if (Network::Players::serverIsPresent(this)) { // remote call Reflection::EventArguments args(1); args[0] = arguments; raiseEventInvocation(event_OnClientEvent, args, NULL); { static boost::once_flag flag = BOOST_ONCE_INIT; boost::call_once(flag, boost::bind(&sendRemoteEventStatsOnServer, "fireAllClients")); } } else { // play solo; fire the event locally to simulate client-server replication onClientEvent(arguments); } } LatchedSignal, shared_ptr)>* RemoteEvent::getOrCreateOnServerEvent(bool create) { if (create && Network::Players::clientIsPresent(this)) throw std::runtime_error("OnServerEvent can only be used on the server"); return &onServerEvent; } LatchedSignal)>* RemoteEvent::getOrCreateOnClientEvent(bool create) { if (create && Network::Players::serverIsPresent(this)) throw std::runtime_error("OnClientEvent can only be used on the client"); return &onClientEvent; } bool RemoteEvent::askSetParent( const Instance* instance ) const { return true; } void RemoteEvent::processRemoteEvent(const Reflection::EventDescriptor& descriptor, const Reflection::EventArguments& args, const SystemAddress& source) { if (&descriptor == &event_OnServerEvent) { RBXASSERT(args.size() == 2); shared_ptr player = args[0].cast >(); // Validate player object; ignore invalid calls for security reasons if (isPlayerValid(player.get(), source)) { EventSource::processRemoteEvent(descriptor, args, source); { static boost::once_flag flag = BOOST_ONCE_INIT; boost::call_once(flag, boost::bind(&sendRemoteEventStatsOnServer, "fireServer")); } } else { RBX::StandardOut::singleton()->printf(RBX::MESSAGE_SENSITIVE, "RemoteEvent::processRemoteEvent: ignore a remote call from %x:%d", source.getAddress(), source.getPort()); } } else { EventSource::processRemoteEvent(descriptor, args, source); } } } // namespace