// send a call int CScriptRMI::ProxyFunction( IFunctionHandler* pH, void *pBuffer, int nSize ) { if (!m_pThis->m_pParent) { pH->GetIScriptSystem()->RaiseError( "Trying to proxy a remote method invocation with no game started... failing" ); return pH->EndFunction(); } string funcInfo; string dispatchInfo; bool gatherDebugInfo = pLogRMIEvents && pLogRMIEvents->GetIVal() != 0; IScriptSystem * pSS = pH->GetIScriptSystem(); const SFunctionInfo * pFunctionInfo = static_cast<const SFunctionInfo *>(pBuffer); SmartScriptTable proxyTable; if (!pH->GetParam( 1, proxyTable )) return pH->EndFunction( false ); ScriptHandle flags; ScriptHandle id; if (!proxyTable->GetValue(FLAGS_FIELD, flags)) return pH->EndFunction( false ); if (!proxyTable->GetValue(ID_FIELD, id)) return pH->EndFunction( false ); int formatStart = 2; if (uint32 flagsMask = flags.n & (eDF_ToClientOnChannel | eDF_ToClientOnOtherChannels)) { if (flagsMask != (eDF_ToClientOnChannel | eDF_ToClientOnOtherChannels)) formatStart ++; } _smart_ptr<CScriptMessage> pMsg = new CScriptMessage( pFunctionInfo->reliability, pFunctionInfo->attachment, (EntityId)id.n, pFunctionInfo->funcID, pFunctionInfo->format ); if (!pMsg->SetFromFunction( pH, formatStart )) { return pH->EndFunction( false ); } if (gatherDebugInfo) funcInfo = pMsg->DebugInfo(); INetContext * pNetContext = m_pThis->m_pParent->GetNetContext(); CCryAction * pFramework = m_pThis->m_pParent->GetFramework(); if (flags.n & eDF_ToServer) { CGameClientNub * pClientNub = pFramework->GetGameClientNub(); bool called = false; if (pClientNub) { CGameClientChannel * pChannel = pClientNub->GetGameClientChannel(); if (pChannel) { DispatchRMI( pChannel->GetNetChannel(), pMsg, false ); called = true; } } if (!called) { pSS->RaiseError( "RMI via client (to server) requested but we are not a client" ); } if (gatherDebugInfo) dispatchInfo = "server"; } else if (flags.n & (eDF_ToClientOnChannel | eDF_ToClientOnOtherChannels)) { CGameServerNub * pServerNub = pFramework->GetGameServerNub(); if (pServerNub) { int myChannelId = 0; bool ok = true; if (flags.n != (eDF_ToClientOnChannel | eDF_ToClientOnOtherChannels)) { if (!pH->GetParam( 2, myChannelId )) { pSS->RaiseError( "RMI onClient or otherClients must have a valid channel id for its first argument" ); ok = false; } else if (pServerNub->GetServerChannelMap()->find(myChannelId) == pServerNub->GetServerChannelMap()->end()) { pSS->RaiseError( "RMI No such server channel %d", myChannelId ); ok = false; } } if (ok) { TServerChannelMap * pChannelMap = pServerNub->GetServerChannelMap(); for (TServerChannelMap::iterator iter = pChannelMap->begin(); iter != pChannelMap->end(); ++iter) { bool isOwn = iter->first == myChannelId; if (isOwn && !(flags.n & eDF_ToClientOnChannel)) continue; if (!isOwn && !(flags.n & eDF_ToClientOnOtherChannels)) continue; if (iter->second->GetNetChannel()) DispatchRMI( iter->second->GetNetChannel(), pMsg, true ); } if (gatherDebugInfo) { dispatchInfo = "client: "; bool appendChannel = false; switch (flags.n) { case eDF_ToClientOnChannel: dispatchInfo += "specificChannel"; appendChannel = true; break; case eDF_ToClientOnOtherChannels: dispatchInfo += "otherChannels"; appendChannel = true; break; case eDF_ToClientOnChannel | eDF_ToClientOnOtherChannels: dispatchInfo += "allChannels"; break; default: dispatchInfo += "UNKNOWN(BUG)"; appendChannel = true; CRY_ASSERT(false); } if (appendChannel) { string temp; temp.Format(" %d", myChannelId); dispatchInfo += temp; } } } } else { pSS->RaiseError( "RMI via server (to client) requested but we are not a server" ); } } if (gatherDebugInfo) { IEntity * pEntity = gEnv->pEntitySystem->GetEntity((EntityId)id.n); const char * name = "<invalid>"; if (pEntity) name = pEntity->GetName(); CryLogAlways("[rmi] %s(%s) [%s] on entity[%d] %s", pH->GetFuncName(), funcInfo.c_str(), dispatchInfo.c_str(), (int)id.n, name); } return pH->EndFunction( true ); }
// build a script dispatch table - this table is the metatable for all // dispatch proxy tables used (onClient, allClients, otherClients) bool CScriptRMI::BuildDispatchTable( SmartScriptTable methods, SmartScriptTable classMethodTable, SmartScriptTable cls, const char * name ) { IScriptSystem * pSS = methods->GetScriptSystem(); SmartScriptTable dispatch( pSS ); uint8 funcID = 0; IScriptTable::SUserFunctionDesc fd; SFunctionInfo info; IScriptTable::Iterator iter = methods->BeginIteration(); while (methods->MoveNext(iter)) { if (iter.sKey) { const char * function = iter.sKey; if (strlen(function)>=2 && function[0] == '_' && function[1] == '_') { methods->EndIteration(iter); pSS->RaiseError( "In Net.Expose: can't expose functions beginning with '__' (function was %s)", function ); return false; } SmartScriptTable specTable; if (!methods->GetValue(function, specTable)) { methods->EndIteration(iter); pSS->RaiseError( "In Net.Expose: function %s entry is not a table (in %s)", function, name ); return false; } // fetch format int count = specTable->Count(); if (count < 1) { methods->EndIteration(iter); pSS->RaiseError( "In Net.Expose: function %s entry is an empty table (in %s)", function, name ); return false; } else if (count-1 > MaxRMIParameters) { methods->EndIteration(iter); pSS->RaiseError( "In Net.Expose: function %s has too many parameters (%d) (in %s)", function, count-1, name ); return false; } int tempReliability; if (!specTable->GetAt(1, tempReliability) || tempReliability < 0 || tempReliability >= eNRT_NumReliabilityTypes) { methods->EndIteration(iter); pSS->RaiseError( "In Net.Expose: function %s has invalid reliability type %d (in %s)", function, tempReliability, name ); return false; } ENetReliabilityType reliability = (ENetReliabilityType) tempReliability; if (!specTable->GetAt(2, tempReliability) || tempReliability < 0 || tempReliability >= eRAT_NumAttachmentTypes) { methods->EndIteration(iter); pSS->RaiseError( "In Net.Expose: function %s has invalid attachment type %d (in %s)", function, tempReliability, name ); } ERMIAttachmentType attachment = (ERMIAttachmentType) tempReliability; string format; format.reserve(count-1); for (int i=3; i<=count; i++) { int type = 666; if (!specTable->GetAt( i, type ) || type<-128 || type>127) { methods->EndIteration(iter); pSS->RaiseError( "In Net.Expose: function %s has invalid serialization policy %d at %d (in %s)", function, type, i, name ); return false; } format.push_back( (char) type ); } CRY_ASSERT( format.length() <= MaxRMIParameters ); strcpy( info.format, format.c_str() ); info.funcID = funcID; info.reliability = reliability; info.attachment = attachment; fd.pUserDataFunc = ProxyFunction; fd.sFunctionName = function; fd.nDataSize = sizeof(SFunctionInfo); fd.pDataBuffer = &info; fd.sGlobalName = "<net-dispatch>"; fd.sFunctionParams = "(...)"; dispatch->AddFunction( fd ); string lookupData = function; lookupData += ":"; lookupData += format; dispatch->SetAt( funcID+1, lookupData.c_str() ); funcID ++; if (funcID == 0) { funcID--; methods->EndIteration(iter); pSS->RaiseError( "Too many functions... max is %d", funcID ); return false; } } else { GameWarning( "In Net.Expose: non-string key ignored" ); } } methods->EndIteration(iter); dispatch->SetValue( VALIDATED_FIELD, false ); cls->SetValue( name, dispatch ); return true; }
// setup the meta-table for synched variables bool CScriptRMI::BuildSynchTable( SmartScriptTable vars, SmartScriptTable cls, const char * name ) { IScriptSystem * pSS = vars->GetScriptSystem(); SmartScriptTable synched( pSS ); SmartScriptTable defaultValues( pSS ); // TODO: Improve IScriptTable::SUserFunctionDesc fd; fd.pFunctor = functor_ret( SynchedNewIndexFunction ); fd.sFunctionName = "__newindex"; fd.sGlobalName = "<net-dispatch>"; fd.sFunctionParams = "(...)"; synched->AddFunction( fd ); std::vector<SSynchedPropertyInfo> properties; IScriptTable::Iterator iter = vars->BeginIteration(); while (vars->MoveNext(iter)) { if (iter.sKey) { int type; if (!vars->GetValue(iter.sKey, type)) { vars->EndIteration(iter); pSS->RaiseError( "No type for %s", iter.sKey ); return false; } size_t len = strlen(iter.sKey); if (len > MaxSynchedPropertyNameLength) { vars->EndIteration(iter); pSS->RaiseError( "Synched var name '%s' too long (max is %d)", iter.sKey, (int)MaxSynchedPropertyNameLength ); return false; } SSynchedPropertyInfo info; strcpy( info.name, iter.sKey ); info.type = (EScriptSerializeType) type; properties.push_back( info ); if (info.type == eSST_String) defaultValues->SetValue( iter.sKey, "" ); else defaultValues->SetValue( iter.sKey, 0 ); } } vars->EndIteration( iter ); if (properties.empty()) return true; fd.pFunctor = NULL; fd.pUserDataFunc = SerializeFunction; fd.nDataSize = sizeof(SSynchedPropertyInfo) * properties.size(); fd.pDataBuffer = &properties[0]; fd.sFunctionName = SERIALIZE_FUNCTION; fd.sFunctionParams = "(...)"; fd.sGlobalName = "<net-dispatch>"; synched->AddFunction( fd ); cls->SetValue( SERVER_SYNCHED_FIELD, synched ); cls->SetValue( "synched", defaultValues ); return true; }
// implementation of Net.Expose() - exposes a class int CScriptRMI::ExposeClass( IFunctionHandler * pFH ) { SmartScriptTable params, cls, clientMethods, serverMethods; SmartScriptTable clientTable, serverTable; SmartScriptTable serverProperties; IScriptSystem * pSS = pFH->GetIScriptSystem(); if (pFH->GetParamCount() != 1) { pSS->RaiseError( "Net.Expose takes only one parameter - a table" ); return pFH->EndFunction(false); } if (!pFH->GetParam( 1, params )) { pSS->RaiseError( "Net.Expose takes only one parameter - a table" ); return pFH->EndFunction(false); } if (!params->GetValue( "Class", cls )) { pSS->RaiseError( "No 'Class' parameter to Net.Expose" ); return pFH->EndFunction(false); } if (!params->GetValue( "ClientMethods", clientMethods )) { GameWarning( "No 'ClientMethods' parameter to Net.Expose" ); } else if (!cls->GetValue("Client", clientTable)) { pSS->RaiseError( "'ClientMethods' exposed, but no 'Client' table in class" ); return pFH->EndFunction(false); } if (!params->GetValue( "ServerMethods", serverMethods )) { GameWarning( "No 'ServerMethods' parameter to Net.Expose" ); } else if (!cls->GetValue("Server", serverTable)) { pSS->RaiseError( "'ServerMethods' exposed, but no 'Server' table in class" ); return pFH->EndFunction(false); } params->GetValue( "ServerProperties", serverProperties ); if (clientMethods.GetPtr()) { CRY_ASSERT( clientTable.GetPtr() ); if (!BuildDispatchTable( clientMethods, clientTable, cls, CLIENT_DISPATCH_FIELD )) return pFH->EndFunction(false); } if (serverMethods.GetPtr()) { CRY_ASSERT( serverTable.GetPtr() ); if (!BuildDispatchTable( serverMethods, serverTable, cls, SERVER_DISPATCH_FIELD )) return pFH->EndFunction(false); } if (serverProperties.GetPtr()) { if (!BuildSynchTable(serverProperties, cls, SERVER_SYNCHED_FIELD)) return pFH->EndFunction(false); } return pFH->EndFunction(true); }