PhotonNetworkPart.cs 111 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572
  1. // ----------------------------------------------------------------------------
  2. // <copyright file="PhotonNetworkPart.cs" company="Exit Games GmbH">
  3. // PhotonNetwork Framework for Unity - Copyright (C) 2018 Exit Games GmbH
  4. // </copyright>
  5. // <summary>
  6. // PhotonNetwork is the central class of the PUN package.
  7. // </summary>
  8. // <author>developer@exitgames.com</author>
  9. // ----------------------------------------------------------------------------
  10. namespace Photon.Pun
  11. {
  12. using System;
  13. using System.Linq;
  14. using UnityEngine;
  15. using System.Collections;
  16. using System.Collections.Generic;
  17. using System.Reflection;
  18. using ExitGames.Client.Photon;
  19. using Photon.Realtime;
  20. using Hashtable = ExitGames.Client.Photon.Hashtable;
  21. using SupportClassPun = ExitGames.Client.Photon.SupportClass;
  22. public static partial class PhotonNetwork
  23. {
  24. private static HashSet<byte> allowedReceivingGroups = new HashSet<byte>();
  25. private static HashSet<byte> blockedSendingGroups = new HashSet<byte>();
  26. private static HashSet<PhotonView> reusablePVHashset = new HashSet<PhotonView>();
  27. /// <summary>
  28. /// The photon view list.
  29. /// </summary>
  30. private static NonAllocDictionary<int, PhotonView> photonViewList = new NonAllocDictionary<int, PhotonView>();
  31. /// <summary>
  32. /// Gets the photon views.
  33. /// </summary>
  34. /// <remarks>
  35. /// This is an expensive operation as it returns a copy of the internal list.
  36. /// </remarks>
  37. /// <value>The photon views.</value>
  38. [System.Obsolete("Use PhotonViewCollection instead for an iterable collection of current photonViews.")]
  39. public static PhotonView[] PhotonViews
  40. {
  41. get
  42. {
  43. var views = new PhotonView[photonViewList.Count];
  44. int idx = 0;
  45. foreach (var v in photonViewList.Values)
  46. {
  47. views[idx] = v;
  48. idx++;
  49. }
  50. return views;
  51. }
  52. }
  53. /// <summary>
  54. /// Returns a new iterable collection of current photon views.
  55. /// </summary>
  56. /// <remarks>
  57. /// You can iterate over all PhotonViews in a simple foreach loop.
  58. /// To use this in a while-loop, assign the new iterator to a variable and then call MoveNext on that.
  59. /// </remarks>
  60. public static NonAllocDictionary<int, PhotonView>.ValueIterator PhotonViewCollection
  61. {
  62. get
  63. {
  64. return photonViewList.Values;
  65. }
  66. }
  67. public static int ViewCount
  68. {
  69. get { return photonViewList.Count; }
  70. }
  71. /// <summary>Parameters: PhotonView for which ownership changed, previous owner of the view.</summary>
  72. private static event Action<PhotonView, Player> OnOwnershipRequestEv;
  73. /// <summary>Parameters: PhotonView for which ownership was requested, player who requests ownership.</summary>
  74. private static event Action<PhotonView, Player> OnOwnershipTransferedEv;
  75. /// <summary>Parameters: PhotonView for which ownership was requested, player who requested (but didn't get) ownership.</summary>
  76. private static event Action<PhotonView, Player> OnOwnershipTransferFailedEv;
  77. /// <summary>
  78. /// Registers an object for callbacks for the implemented callback-interfaces.
  79. /// </summary>
  80. /// <remarks>
  81. /// The covered callback interfaces are: IConnectionCallbacks, IMatchmakingCallbacks,
  82. /// ILobbyCallbacks, IInRoomCallbacks, IOnEventCallback and IWebRpcCallback.
  83. ///
  84. /// See: <a href="https://doc.photonengine.com/en-us/pun/v2/getting-started/dotnet-callbacks">.Net Callbacks</a>
  85. /// </remarks>
  86. /// <param name="target">The object that registers to get callbacks from PUN's LoadBalancingClient.</param>
  87. public static void AddCallbackTarget(object target)
  88. {
  89. if (target is PhotonView)
  90. {
  91. return;
  92. }
  93. IPunOwnershipCallbacks punOwnershipCallback = target as IPunOwnershipCallbacks;
  94. if (punOwnershipCallback != null)
  95. {
  96. OnOwnershipRequestEv += punOwnershipCallback.OnOwnershipRequest;
  97. OnOwnershipTransferedEv += punOwnershipCallback.OnOwnershipTransfered;
  98. OnOwnershipTransferFailedEv += punOwnershipCallback.OnOwnershipTransferFailed;
  99. }
  100. NetworkingClient.AddCallbackTarget(target);
  101. }
  102. /// <summary>
  103. /// Removes the target object from callbacks for its implemented callback-interfaces.
  104. /// </summary>
  105. /// <remarks>
  106. /// The covered callback interfaces are: IConnectionCallbacks, IMatchmakingCallbacks,
  107. /// ILobbyCallbacks, IInRoomCallbacks, IOnEventCallback and IWebRpcCallback.
  108. ///
  109. /// See: <a href="https://doc.photonengine.com/en-us/pun/v2/getting-started/dotnet-callbacks">.Net Callbacks</a>
  110. /// </remarks>
  111. /// <param name="target">The object that unregisters from getting callbacks.</param>
  112. public static void RemoveCallbackTarget(object target)
  113. {
  114. if (target is PhotonView || NetworkingClient == null)
  115. {
  116. return;
  117. }
  118. IPunOwnershipCallbacks punOwnershipCallback = target as IPunOwnershipCallbacks;
  119. if (punOwnershipCallback != null)
  120. {
  121. OnOwnershipRequestEv -= punOwnershipCallback.OnOwnershipRequest;
  122. OnOwnershipTransferedEv -= punOwnershipCallback.OnOwnershipTransfered;
  123. OnOwnershipTransferFailedEv -= punOwnershipCallback.OnOwnershipTransferFailed;
  124. }
  125. NetworkingClient.RemoveCallbackTarget(target);
  126. }
  127. internal static string CallbacksToString()
  128. {
  129. var x = NetworkingClient.ConnectionCallbackTargets.Select(m => m.ToString()).ToArray();
  130. return string.Join(", ", x);
  131. }
  132. internal static byte currentLevelPrefix = 0;
  133. /// <summary>Internally used to flag if the message queue was disabled by a "scene sync" situation (to re-enable it).</summary>
  134. internal static bool loadingLevelAndPausedNetwork = false;
  135. /// <summary>For automatic scene syncing, the loaded scene is put into a room property. This is the name of said prop.</summary>
  136. internal const string CurrentSceneProperty = "curScn";
  137. internal const string CurrentScenePropertyLoadAsync = "curScnLa";
  138. /// <summary>
  139. /// An Object Pool can be used to keep and reuse instantiated object instances. Replaces Unity's default Instantiate and Destroy methods.
  140. /// </summary>
  141. /// <remarks>
  142. /// Defaults to the DefaultPool type.
  143. /// To use a GameObject pool, implement IPunPrefabPool and assign it here.
  144. /// Prefabs are identified by name.
  145. /// </remarks>
  146. public static IPunPrefabPool PrefabPool
  147. {
  148. get
  149. {
  150. return prefabPool;
  151. }
  152. set
  153. {
  154. if (value == null)
  155. {
  156. Debug.LogWarning("PhotonNetwork.PrefabPool cannot be set to null. It will default back to using the 'DefaultPool' Pool");
  157. prefabPool = new DefaultPool();
  158. }
  159. else
  160. {
  161. prefabPool = value;
  162. }
  163. }
  164. }
  165. private static IPunPrefabPool prefabPool;
  166. /// <summary>
  167. /// While enabled, the MonoBehaviours on which we call RPCs are cached, avoiding costly GetComponents&lt;MonoBehaviour&gt;() calls.
  168. /// </summary>
  169. /// <remarks>
  170. /// RPCs are called on the MonoBehaviours of a target PhotonView. Those have to be found via GetComponents.
  171. ///
  172. /// When set this to true, the list of MonoBehaviours gets cached in each PhotonView.
  173. /// You can use photonView.RefreshRpcMonoBehaviourCache() to manually refresh a PhotonView's
  174. /// list of MonoBehaviours on demand (when a new MonoBehaviour gets added to a networked GameObject, e.g.).
  175. /// </remarks>
  176. public static bool UseRpcMonoBehaviourCache;
  177. private static readonly Dictionary<Type, List<MethodInfo>> monoRPCMethodsCache = new Dictionary<Type, List<MethodInfo>>();
  178. private static Dictionary<string, int> rpcShortcuts; // lookup "table" for the index (shortcut) of an RPC name
  179. /// <summary>
  180. /// If an RPC method is implemented as coroutine, it gets started, unless this value is false.
  181. /// </summary>
  182. /// <remarks>
  183. /// As starting coroutines causes a little memnory garbage, you may want to disable this option but it is
  184. /// also good enough to not return IEnumerable from methods with the attribute PunRPC.
  185. /// </remarks>
  186. public static bool RunRpcCoroutines = true;
  187. // for asynchronous network synched loading.
  188. private static AsyncOperation _AsyncLevelLoadingOperation;
  189. private static float _levelLoadingProgress = 0f;
  190. /// <summary>
  191. /// Represents the scene loading progress when using LoadLevel().
  192. /// </summary>
  193. /// <remarks>
  194. /// The value is 0 if the app never loaded a scene with LoadLevel().</br>
  195. /// During async scene loading, the value is between 0 and 1.</br>
  196. /// Once any scene completed loading, it stays at 1 (signaling "done").</br>
  197. /// </remarks>
  198. /// <value>The level loading progress. Ranges from 0 to 1.</value>
  199. public static float LevelLoadingProgress
  200. {
  201. get
  202. {
  203. if (_AsyncLevelLoadingOperation != null)
  204. {
  205. _levelLoadingProgress = _AsyncLevelLoadingOperation.progress;
  206. }
  207. else if (_levelLoadingProgress > 0f)
  208. {
  209. _levelLoadingProgress = 1f;
  210. }
  211. return _levelLoadingProgress;
  212. }
  213. }
  214. /// <summary>
  215. /// Called when "this client" left a room to clean up.
  216. /// </summary>
  217. /// <remarks>
  218. /// if (Server == ServerConnection.GameServer && (state == ClientState.Disconnecting || state == ClientState.DisconnectingFromGameServer))
  219. /// </remarks>
  220. private static void LeftRoomCleanup()
  221. {
  222. // Clean up if we were loading asynchronously.
  223. if (_AsyncLevelLoadingOperation != null)
  224. {
  225. _AsyncLevelLoadingOperation.allowSceneActivation = false;
  226. _AsyncLevelLoadingOperation = null;
  227. }
  228. rpcEvent.Clear(); // none of the last RPC parameters are needed anymore
  229. bool wasInRoom = NetworkingClient.CurrentRoom != null;
  230. // when leaving a room, we clean up depending on that room's settings.
  231. bool autoCleanupSettingOfRoom = wasInRoom && CurrentRoom.AutoCleanUp;
  232. allowedReceivingGroups = new HashSet<byte>();
  233. blockedSendingGroups = new HashSet<byte>();
  234. // Cleanup all network objects (all spawned PhotonViews, local and remote)
  235. if (autoCleanupSettingOfRoom || offlineModeRoom != null)
  236. {
  237. LocalCleanupAnythingInstantiated(true);
  238. }
  239. }
  240. /// <summary>
  241. /// Cleans up anything that was instantiated in-game (not loaded with the scene). Resets views that are not destroyed.
  242. /// </summary>
  243. // TODO: This method name no longer matches is function. It also resets room object's views.
  244. internal static void LocalCleanupAnythingInstantiated(bool destroyInstantiatedGameObjects)
  245. {
  246. //if (tempInstantiationData.Count > 0)
  247. //{
  248. // Debug.LogWarning("It seems some instantiation is not completed, as instantiation data is used. You should make sure instantiations are paused when calling this method. Cleaning now, despite ");
  249. //}
  250. // Destroy GO's (if we should)
  251. if (destroyInstantiatedGameObjects)
  252. {
  253. // Fill list with Instantiated objects
  254. HashSet<GameObject> instantiatedGos = new HashSet<GameObject>();
  255. foreach (PhotonView view in photonViewList.Values)
  256. {
  257. if (view.isRuntimeInstantiated)
  258. {
  259. instantiatedGos.Add(view.gameObject); // HashSet keeps each object only once
  260. }
  261. // For non-instantiated objects (scene objects) - reset the view
  262. else
  263. {
  264. view.ResetPhotonView(true);
  265. }
  266. }
  267. foreach (GameObject go in instantiatedGos)
  268. {
  269. RemoveInstantiatedGO(go, true);
  270. }
  271. }
  272. // photonViewList is cleared of anything instantiated (so scene items are left inside)
  273. // any other lists can be
  274. PhotonNetwork.lastUsedViewSubId = 0;
  275. PhotonNetwork.lastUsedViewSubIdStatic = 0;
  276. }
  277. /// <summary>
  278. /// Resets the PhotonView "lastOnSerializeDataSent" so that "OnReliable" synched PhotonViews send a complete state to new clients (if the state doesnt change, no messages would be send otherwise!).
  279. /// Note that due to this reset, ALL other players will receive the full OnSerialize.
  280. /// </summary>
  281. private static void ResetPhotonViewsOnSerialize()
  282. {
  283. foreach (PhotonView photonView in photonViewList.Values)
  284. {
  285. photonView.lastOnSerializeDataSent = null;
  286. }
  287. }
  288. // PHOTONVIEW/RPC related
  289. #pragma warning disable 0414
  290. private static readonly Type typePunRPC = typeof(PunRPC);
  291. private static readonly Type typePhotonMessageInfo = typeof(PhotonMessageInfo);
  292. private static readonly object keyByteZero = (byte)0;
  293. private static readonly object keyByteOne = (byte)1;
  294. private static readonly object keyByteTwo = (byte)2;
  295. private static readonly object keyByteThree = (byte)3;
  296. private static readonly object keyByteFour = (byte)4;
  297. private static readonly object keyByteFive = (byte)5;
  298. private static readonly object keyByteSix = (byte)6;
  299. private static readonly object keyByteSeven = (byte)7;
  300. private static readonly object keyByteEight = (byte)8;
  301. private static readonly object[] emptyObjectArray = new object[0];
  302. private static readonly Type[] emptyTypeArray = new Type[0];
  303. #pragma warning restore 0414
  304. /// <summary>
  305. /// Executes a received RPC event
  306. /// </summary>
  307. internal static void ExecuteRpc(Hashtable rpcData, Player sender)
  308. {
  309. if (rpcData == null || !rpcData.ContainsKey(keyByteZero))
  310. {
  311. Debug.LogError("Malformed RPC; this should never occur. Content: " + SupportClassPun.DictionaryToString(rpcData));
  312. return;
  313. }
  314. // ts: updated with "flat" event data
  315. int netViewID = (int)rpcData[keyByteZero]; // LIMITS PHOTONVIEWS&PLAYERS
  316. int otherSidePrefix = 0; // by default, the prefix is 0 (and this is not being sent)
  317. if (rpcData.ContainsKey(keyByteOne))
  318. {
  319. otherSidePrefix = (short)rpcData[keyByteOne];
  320. }
  321. string inMethodName;
  322. if (rpcData.ContainsKey(keyByteFive))
  323. {
  324. int rpcIndex = (byte)rpcData[keyByteFive]; // LIMITS RPC COUNT
  325. if (rpcIndex > PhotonNetwork.PhotonServerSettings.RpcList.Count - 1)
  326. {
  327. Debug.LogError("Could not find RPC with index: " + rpcIndex + ". Going to ignore! Check PhotonServerSettings.RpcList");
  328. return;
  329. }
  330. else
  331. {
  332. inMethodName = PhotonNetwork.PhotonServerSettings.RpcList[rpcIndex];
  333. }
  334. }
  335. else
  336. {
  337. inMethodName = (string)rpcData[keyByteThree];
  338. }
  339. object[] arguments = null;
  340. if (rpcData.ContainsKey(keyByteFour))
  341. {
  342. arguments = (object[])rpcData[keyByteFour];
  343. }
  344. PhotonView photonNetview = GetPhotonView(netViewID);
  345. if (photonNetview == null)
  346. {
  347. int viewOwnerId = netViewID / PhotonNetwork.MAX_VIEW_IDS;
  348. bool owningPv = (viewOwnerId == NetworkingClient.LocalPlayer.ActorNumber);
  349. bool ownerSent = sender != null && viewOwnerId == sender.ActorNumber;
  350. if (owningPv)
  351. {
  352. Debug.LogWarning("Received RPC \"" + inMethodName + "\" for viewID " + netViewID + " but this PhotonView does not exist! View was/is ours." + (ownerSent ? " Owner called." : " Remote called.") + " By: " + sender);
  353. }
  354. else
  355. {
  356. Debug.LogWarning("Received RPC \"" + inMethodName + "\" for viewID " + netViewID + " but this PhotonView does not exist! Was remote PV." + (ownerSent ? " Owner called." : " Remote called.") + " By: " + sender + " Maybe GO was destroyed but RPC not cleaned up.");
  357. }
  358. return;
  359. }
  360. if (photonNetview.Prefix != otherSidePrefix)
  361. {
  362. Debug.LogError("Received RPC \"" + inMethodName + "\" on viewID " + netViewID + " with a prefix of " + otherSidePrefix + ", our prefix is " + photonNetview.Prefix + ". The RPC has been ignored.");
  363. return;
  364. }
  365. // Get method name
  366. if (string.IsNullOrEmpty(inMethodName))
  367. {
  368. Debug.LogError("Malformed RPC; this should never occur. Content: " + SupportClassPun.DictionaryToString(rpcData));
  369. return;
  370. }
  371. if (PhotonNetwork.LogLevel >= PunLogLevel.Full)
  372. {
  373. Debug.Log("Received RPC: " + inMethodName);
  374. }
  375. // SetReceiving filtering
  376. if (photonNetview.Group != 0 && !allowedReceivingGroups.Contains(photonNetview.Group))
  377. {
  378. return; // Ignore group
  379. }
  380. Type[] argumentsTypes = null;
  381. if (arguments != null && arguments.Length > 0)
  382. {
  383. argumentsTypes = new Type[arguments.Length];
  384. int i = 0;
  385. for (int index = 0; index < arguments.Length; index++)
  386. {
  387. object objX = arguments[index];
  388. if (objX == null)
  389. {
  390. argumentsTypes[i] = null;
  391. }
  392. else
  393. {
  394. argumentsTypes[i] = objX.GetType();
  395. }
  396. i++;
  397. }
  398. }
  399. int receivers = 0;
  400. int foundMethods = 0;
  401. if (!PhotonNetwork.UseRpcMonoBehaviourCache || photonNetview.RpcMonoBehaviours == null || photonNetview.RpcMonoBehaviours.Length == 0)
  402. {
  403. photonNetview.RefreshRpcMonoBehaviourCache();
  404. }
  405. for (int componentsIndex = 0; componentsIndex < photonNetview.RpcMonoBehaviours.Length; componentsIndex++)
  406. {
  407. MonoBehaviour monob = photonNetview.RpcMonoBehaviours[componentsIndex];
  408. if (monob == null)
  409. {
  410. Debug.LogError("ERROR You have missing MonoBehaviours on your gameobjects!");
  411. continue;
  412. }
  413. Type type = monob.GetType();
  414. // Get [PunRPC] methods from cache
  415. List<MethodInfo> cachedRPCMethods = null;
  416. bool methodsOfTypeInCache = monoRPCMethodsCache.TryGetValue(type, out cachedRPCMethods);
  417. if (!methodsOfTypeInCache)
  418. {
  419. List<MethodInfo> entries = SupportClassPun.GetMethods(type, typePunRPC);
  420. monoRPCMethodsCache[type] = entries;
  421. cachedRPCMethods = entries;
  422. }
  423. if (cachedRPCMethods == null)
  424. {
  425. continue;
  426. }
  427. // Check cache for valid methodname+arguments
  428. for (int index = 0; index < cachedRPCMethods.Count; index++)
  429. {
  430. MethodInfo mInfo = cachedRPCMethods[index];
  431. if (!mInfo.Name.Equals(inMethodName))
  432. {
  433. continue;
  434. }
  435. ParameterInfo[] parameters = mInfo.GetCachedParemeters();
  436. foundMethods++;
  437. // if we got no arguments:
  438. if (arguments == null)
  439. {
  440. if (parameters.Length == 0)
  441. {
  442. receivers++;
  443. object o = mInfo.Invoke((object)monob, null);
  444. if (PhotonNetwork.RunRpcCoroutines)
  445. {
  446. IEnumerator ie = null;//o as IEnumerator;
  447. if ((ie = o as IEnumerator) != null)
  448. {
  449. PhotonHandler.Instance.StartCoroutine(ie);
  450. }
  451. }
  452. }
  453. else if (parameters.Length == 1 && parameters[0].ParameterType == typeof(PhotonMessageInfo))
  454. {
  455. int sendTime = (int)rpcData[keyByteTwo];
  456. receivers++;
  457. object o = mInfo.Invoke((object)monob, new object[] { new PhotonMessageInfo(sender, sendTime, photonNetview) });
  458. if (PhotonNetwork.RunRpcCoroutines)
  459. {
  460. IEnumerator ie = null;//o as IEnumerator;
  461. if ((ie = o as IEnumerator) != null)
  462. {
  463. PhotonHandler.Instance.StartCoroutine(ie);
  464. }
  465. }
  466. }
  467. continue;
  468. }
  469. // if there are any arguments (in the incoming call check if the method is compatible
  470. if (parameters.Length == arguments.Length)
  471. {
  472. // Normal, PhotonNetworkMessage left out
  473. if (CheckTypeMatch(parameters, argumentsTypes))
  474. {
  475. receivers++;
  476. object o = mInfo.Invoke((object)monob, arguments);
  477. if (PhotonNetwork.RunRpcCoroutines)
  478. {
  479. IEnumerator ie = null;//o as IEnumerator;
  480. if ((ie = o as IEnumerator) != null)
  481. {
  482. PhotonHandler.Instance.StartCoroutine(ie);
  483. }
  484. }
  485. }
  486. continue;
  487. }
  488. if (parameters.Length == arguments.Length + 1)
  489. {
  490. // Check for PhotonNetworkMessage being the last
  491. if (parameters[parameters.Length - 1].ParameterType == typeof(PhotonMessageInfo) && CheckTypeMatch(parameters, argumentsTypes))
  492. {
  493. int sendTime = (int)rpcData[keyByteTwo];
  494. object[] argumentsWithInfo = new object[arguments.Length + 1];
  495. arguments.CopyTo(argumentsWithInfo, 0);
  496. argumentsWithInfo[argumentsWithInfo.Length - 1] = new PhotonMessageInfo(sender, sendTime, photonNetview);
  497. receivers++;
  498. object o = mInfo.Invoke((object)monob, argumentsWithInfo);
  499. if (PhotonNetwork.RunRpcCoroutines)
  500. {
  501. IEnumerator ie = null;//o as IEnumerator;
  502. if ((ie = o as IEnumerator) != null)
  503. {
  504. PhotonHandler.Instance.StartCoroutine(ie);
  505. }
  506. }
  507. }
  508. continue;
  509. }
  510. if (parameters.Length == 1 && parameters[0].ParameterType.IsArray)
  511. {
  512. receivers++;
  513. object o = mInfo.Invoke((object)monob, new object[] { arguments });
  514. if (PhotonNetwork.RunRpcCoroutines)
  515. {
  516. IEnumerator ie = null;//o as IEnumerator;
  517. if ((ie = o as IEnumerator) != null)
  518. {
  519. PhotonHandler.Instance.StartCoroutine(ie);
  520. }
  521. }
  522. continue;
  523. }
  524. }
  525. }
  526. // Error handling
  527. if (receivers != 1)
  528. {
  529. string argsString = string.Empty;
  530. int argsLength = 0;
  531. if (argumentsTypes != null)
  532. {
  533. argsLength = argumentsTypes.Length;
  534. for (int index = 0; index < argumentsTypes.Length; index++)
  535. {
  536. Type ty = argumentsTypes[index];
  537. if (argsString != string.Empty)
  538. {
  539. argsString += ", ";
  540. }
  541. if (ty == null)
  542. {
  543. argsString += "null";
  544. }
  545. else
  546. {
  547. argsString += ty.Name;
  548. }
  549. }
  550. }
  551. GameObject context = photonNetview != null ? photonNetview.gameObject : null;
  552. if (receivers == 0)
  553. {
  554. if (foundMethods == 0)
  555. {
  556. // found no method that matches
  557. Debug.LogErrorFormat(context, "RPC method '{0}({2})' not found on object with PhotonView {1}. Implement as non-static. Apply [PunRPC]. Components on children are not found. " +
  558. "Return type must be void or IEnumerator (if you enable RunRpcCoroutines). RPCs are a one-way message.", inMethodName, netViewID, argsString);
  559. }
  560. else
  561. {
  562. // found a method but not the right arguments
  563. Debug.LogErrorFormat(context, "RPC method '{0}' found on object with PhotonView {1} but has wrong parameters. Implement as '{0}({2})'. PhotonMessageInfo is optional as final parameter." +
  564. "Return type must be void or IEnumerator (if you enable RunRpcCoroutines).", inMethodName, netViewID, argsString);
  565. }
  566. }
  567. else
  568. {
  569. // multiple components have the same method
  570. Debug.LogErrorFormat(context, "RPC method '{0}({2})' found {3}x on object with PhotonView {1}. Only one component should implement it." +
  571. "Return type must be void or IEnumerator (if you enable RunRpcCoroutines).", inMethodName, netViewID, argsString, foundMethods);
  572. }
  573. }
  574. }
  575. /// <summary>
  576. /// Check if all types match with parameters. We can have more paramters then types (allow last RPC type to be different).
  577. /// </summary>
  578. /// <param name="methodParameters"></param>
  579. /// <param name="callParameterTypes"></param>
  580. /// <returns>If the types-array has matching parameters (of method) in the parameters array (which may be longer).</returns>
  581. private static bool CheckTypeMatch(ParameterInfo[] methodParameters, Type[] callParameterTypes)
  582. {
  583. if (methodParameters.Length < callParameterTypes.Length)
  584. {
  585. return false;
  586. }
  587. for (int index = 0; index < callParameterTypes.Length; index++)
  588. {
  589. #if NETFX_CORE
  590. TypeInfo methodParamTI = methodParameters[index].ParameterType.GetTypeInfo();
  591. TypeInfo callParamTI = callParameterTypes[index].GetTypeInfo();
  592. if (callParameterTypes[index] != null && !methodParamTI.IsAssignableFrom(callParamTI) && !(callParamTI.IsEnum && System.Enum.GetUnderlyingType(methodParamTI.AsType()).GetTypeInfo().IsAssignableFrom(callParamTI)))
  593. {
  594. return false;
  595. }
  596. #else
  597. Type type = methodParameters[index].ParameterType;
  598. if (callParameterTypes[index] != null && !type.IsAssignableFrom(callParameterTypes[index]) && !(type.IsEnum && System.Enum.GetUnderlyingType(type).IsAssignableFrom(callParameterTypes[index])))
  599. {
  600. return false;
  601. }
  602. #endif
  603. }
  604. return true;
  605. }
  606. /// <summary>
  607. /// Destroys all Instantiates and RPCs locally and (if not localOnly) sends EvDestroy(player) and clears related events in the server buffer.
  608. /// </summary>
  609. public static void DestroyPlayerObjects(int playerId, bool localOnly)
  610. {
  611. if (playerId <= 0)
  612. {
  613. Debug.LogError("Failed to Destroy objects of playerId: " + playerId);
  614. return;
  615. }
  616. if (!localOnly)
  617. {
  618. // clean server's Instantiate and RPC buffers
  619. OpRemoveFromServerInstantiationsOfPlayer(playerId);
  620. OpCleanActorRpcBuffer(playerId);
  621. // send Destroy(player) to anyone else
  622. SendDestroyOfPlayer(playerId);
  623. }
  624. // locally cleaning up that player's objects
  625. HashSet<GameObject> playersGameObjects = new HashSet<GameObject>();
  626. // with ownership transfer, some objects might lose their owner.
  627. // in that case, the creator becomes the owner again. every client can apply done below.
  628. foreach (PhotonView view in photonViewList.Values)
  629. {
  630. if (view == null)
  631. {
  632. Debug.LogError("Null view");
  633. continue;
  634. }
  635. // Mark player created objects for destruction
  636. if (view.CreatorActorNr == playerId)
  637. {
  638. playersGameObjects.Add(view.gameObject);
  639. continue;
  640. }
  641. if (view.OwnerActorNr == playerId)
  642. {
  643. var previousOwner = view.Owner;
  644. view.OwnerActorNr = view.CreatorActorNr;
  645. view.ControllerActorNr = view.CreatorActorNr;
  646. // This callback was not originally here. Added with the IsMine caching changes.
  647. if (PhotonNetwork.OnOwnershipTransferedEv != null)
  648. {
  649. PhotonNetwork.OnOwnershipTransferedEv(view, previousOwner);
  650. }
  651. }
  652. }
  653. // any non-local work is already done, so with the list of that player's objects, we can clean up (locally only)
  654. foreach (GameObject gameObject in playersGameObjects)
  655. {
  656. RemoveInstantiatedGO(gameObject, true);
  657. }
  658. }
  659. public static void DestroyAll(bool localOnly)
  660. {
  661. if (!localOnly)
  662. {
  663. OpRemoveCompleteCache();
  664. SendDestroyOfAll();
  665. }
  666. LocalCleanupAnythingInstantiated(true);
  667. }
  668. internal static List<PhotonView> foundPVs = new List<PhotonView>();
  669. /// <summary>Removes GameObject and the PhotonViews on it from local lists and optionally updates remotes. GameObject gets destroyed at end.</summary>
  670. /// <remarks>
  671. /// This method might fail and quit early due to several tests.
  672. /// </remarks>
  673. /// <param name="go">GameObject to cleanup.</param>
  674. /// <param name="localOnly">For localOnly, tests of control are skipped and the server is not updated.</param>
  675. internal static void RemoveInstantiatedGO(GameObject go, bool localOnly)
  676. {
  677. // Avoid cleanup if we are quitting.
  678. if (ConnectionHandler.AppQuits)
  679. return;
  680. if (go == null)
  681. {
  682. Debug.LogError("Failed to 'network-remove' GameObject because it's null.");
  683. return;
  684. }
  685. // Don't remove the GO if it doesn't have any PhotonView
  686. go.GetComponentsInChildren<PhotonView>(true, foundPVs);
  687. if (foundPVs.Count <= 0)
  688. {
  689. Debug.LogError("Failed to 'network-remove' GameObject because has no PhotonView components: " + go);
  690. return;
  691. }
  692. PhotonView viewZero = foundPVs[0];
  693. // Don't remove GOs that are owned by others (unless this is the master and the remote player left)
  694. if (!localOnly)
  695. {
  696. //Debug.LogWarning("Destroy " + instantiationId + " creator " + creatorId, go);
  697. if (!viewZero.IsMine)
  698. {
  699. Debug.LogError("Failed to 'network-remove' GameObject. Client is neither owner nor MasterClient taking over for owner who left: " + viewZero);
  700. foundPVs.Clear(); // as foundPVs is re-used, clean it to avoid lingering references
  701. return;
  702. }
  703. }
  704. // cleanup instantiation (event and local list)
  705. if (!localOnly)
  706. {
  707. ServerCleanInstantiateAndDestroy(viewZero); // server cleaning
  708. }
  709. int creatorActorNr = viewZero.CreatorActorNr;
  710. // cleanup PhotonViews and their RPCs events (if not localOnly)
  711. for (int j = foundPVs.Count - 1; j >= 0; j--)
  712. {
  713. PhotonView view = foundPVs[j];
  714. if (view == null)
  715. {
  716. continue;
  717. }
  718. // TODO: Probably should have a enum that defines when auto-detachment should occur.
  719. // Check nested PVs for different creator. Detach if different, to avoid destroying reparanted objects.
  720. if (j != 0)
  721. {
  722. // view does not belong to the same object as the root PV - unparent this nested PV to avoid destruction.
  723. if (view.CreatorActorNr != creatorActorNr)
  724. {
  725. view.transform.SetParent(null, true);
  726. continue;
  727. }
  728. }
  729. // Notify all children PVs of impending destruction. Send the root PV (the actual object getting destroyed) to the callbacks.
  730. view.OnPreNetDestroy(viewZero);
  731. // we only destroy/clean PhotonViews that were created by PhotonNetwork.Instantiate (and those have an instantiationId!)
  732. if (view.InstantiationId >= 1)
  733. {
  734. LocalCleanPhotonView(view);
  735. }
  736. if (!localOnly)
  737. {
  738. OpCleanRpcBuffer(view);
  739. }
  740. }
  741. if (PhotonNetwork.LogLevel >= PunLogLevel.Full)
  742. {
  743. Debug.Log("Network destroy Instantiated GO: " + go.name);
  744. }
  745. foundPVs.Clear(); // as foundPVs is re-used, clean it to avoid lingering references
  746. go.SetActive(false); // PUN 2 disables objects before the return to the pool
  747. prefabPool.Destroy(go); // PUN 2 always uses a PrefabPool (even for the default implementation)
  748. }
  749. private static readonly ExitGames.Client.Photon.Hashtable removeFilter = new ExitGames.Client.Photon.Hashtable();
  750. private static readonly ExitGames.Client.Photon.Hashtable ServerCleanDestroyEvent = new ExitGames.Client.Photon.Hashtable();
  751. private static readonly RaiseEventOptions ServerCleanOptions = new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache };
  752. internal static RaiseEventOptions SendToAllOptions = new RaiseEventOptions() { Receivers = ReceiverGroup.All };
  753. internal static RaiseEventOptions SendToOthersOptions = new RaiseEventOptions() { Receivers = ReceiverGroup.Others };
  754. internal static RaiseEventOptions SendToSingleOptions = new RaiseEventOptions() { TargetActors = new int[1] };
  755. /// <summary>
  756. /// Removes an instantiation event from the server's cache. Needs id and actorNr of player who instantiated.
  757. /// </summary>
  758. private static void ServerCleanInstantiateAndDestroy(PhotonView photonView)
  759. {
  760. int filterId;
  761. if (photonView.isRuntimeInstantiated)
  762. {
  763. filterId = photonView.InstantiationId; // actual, live InstantiationIds start with 1 and go up
  764. // remove the Instantiate-event from the server cache:
  765. removeFilter[keyByteSeven] = filterId;
  766. ServerCleanOptions.CachingOption = EventCaching.RemoveFromRoomCache;
  767. PhotonNetwork.RaiseEventInternal(PunEvent.Instantiation, removeFilter, ServerCleanOptions, SendOptions.SendReliable);
  768. }
  769. // Don't remove the Instantiation from the server, if it doesn't have a proper ID
  770. else
  771. {
  772. filterId = photonView.ViewID;
  773. }
  774. // send a Destroy-event to everyone (removing an event from the cache, doesn't send this to anyone else):
  775. ServerCleanDestroyEvent[keyByteZero] = filterId;
  776. ServerCleanOptions.CachingOption = photonView.isRuntimeInstantiated ? EventCaching.DoNotCache : EventCaching.AddToRoomCacheGlobal; // if the view got loaded with the scene, cache EvDestroy for anyone (re)joining later
  777. PhotonNetwork.RaiseEventInternal(PunEvent.Destroy, ServerCleanDestroyEvent, ServerCleanOptions, SendOptions.SendReliable);
  778. }
  779. private static void SendDestroyOfPlayer(int actorNr)
  780. {
  781. ExitGames.Client.Photon.Hashtable evData = new ExitGames.Client.Photon.Hashtable();
  782. evData[keyByteZero] = actorNr;
  783. PhotonNetwork.RaiseEventInternal(PunEvent.DestroyPlayer, evData, null, SendOptions.SendReliable);
  784. }
  785. private static void SendDestroyOfAll()
  786. {
  787. ExitGames.Client.Photon.Hashtable evData = new ExitGames.Client.Photon.Hashtable();
  788. evData[keyByteZero] = -1;
  789. PhotonNetwork.RaiseEventInternal(PunEvent.DestroyPlayer, evData, null, SendOptions.SendReliable);
  790. }
  791. private static void OpRemoveFromServerInstantiationsOfPlayer(int actorNr)
  792. {
  793. // removes all "Instantiation" events of player actorNr. this is not an event for anyone else
  794. RaiseEventOptions options = new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache, TargetActors = new int[] { actorNr } };
  795. PhotonNetwork.RaiseEventInternal(PunEvent.Instantiation, null, options, SendOptions.SendReliable);
  796. }
  797. internal static void RequestOwnership(int viewID, int fromOwner)
  798. {
  799. //Debug.Log("RequestOwnership(): " + viewID + " from: " + fromOwner + " Time: " + Environment.TickCount % 1000);
  800. PhotonNetwork.RaiseEventInternal(PunEvent.OwnershipRequest, new int[] { viewID, fromOwner }, SendToAllOptions, SendOptions.SendReliable);
  801. }
  802. internal static void TransferOwnership(int viewID, int playerID)
  803. {
  804. //Debug.Log("TransferOwnership() view " + viewID + " to: " + playerID + " Time: " + Environment.TickCount % 1000);
  805. PhotonNetwork.RaiseEventInternal(PunEvent.OwnershipTransfer, new int[] { viewID, playerID }, SendToAllOptions, SendOptions.SendReliable);
  806. }
  807. /// <summary>
  808. /// Call this on the Master to reassert ownership on clients. viewOwnerPairs are [viewId][viewOwnerActorNr] pairs. targetActor of -1 indicates send to all others.
  809. /// </summary>
  810. internal static void OwnershipUpdate(int[] viewOwnerPairs, int targetActor = -1)
  811. {
  812. RaiseEventOptions opts;
  813. if (targetActor == -1)
  814. {
  815. opts = SendToOthersOptions;
  816. }
  817. else
  818. {
  819. SendToSingleOptions.TargetActors[0] = targetActor;
  820. opts = SendToSingleOptions;
  821. }
  822. PhotonNetwork.RaiseEventInternal(PunEvent.OwnershipUpdate, viewOwnerPairs, opts, SendOptions.SendReliable);
  823. }
  824. public static bool LocalCleanPhotonView(PhotonView view)
  825. {
  826. view.removedFromLocalViewList = true;
  827. return photonViewList.Remove(view.ViewID);
  828. }
  829. public static PhotonView GetPhotonView(int viewID)
  830. {
  831. PhotonView result = null;
  832. photonViewList.TryGetValue(viewID, out result);
  833. /// Removed aggressive find that likely had no real use case, and was expensive.
  834. //if (result == null)
  835. //{
  836. // PhotonView[] views = GameObject.FindObjectsOfType(typeof(PhotonView)) as PhotonView[];
  837. // for (int i = 0; i < views.Length; i++)
  838. // {
  839. // PhotonView view = views[i];
  840. // if (view.ViewID == viewID)
  841. // {
  842. // if (view.didAwake)
  843. // {
  844. // Debug.LogWarning("Had to lookup view that wasn't in photonViewList: " + view);
  845. // }
  846. // return view;
  847. // }
  848. // }
  849. //}
  850. return result;
  851. }
  852. public static void RegisterPhotonView(PhotonView netView)
  853. {
  854. if (!Application.isPlaying)
  855. {
  856. photonViewList = new NonAllocDictionary<int, PhotonView>();
  857. return;
  858. }
  859. if (netView.ViewID == 0)
  860. {
  861. // don't register views with ID 0 (not initialized). they register when a ID is assigned later on
  862. Debug.Log("PhotonView register is ignored, because viewID is 0. No id assigned yet to: " + netView);
  863. return;
  864. }
  865. PhotonView listedView = null;
  866. bool isViewListed = photonViewList.TryGetValue(netView.ViewID, out listedView);
  867. if (isViewListed)
  868. {
  869. // if some other view is in the list already, we got a problem. it might be indestructible. print out error
  870. if (netView != listedView)
  871. {
  872. Debug.LogError(string.Format("PhotonView ID duplicate found: {0}. New: {1} old: {2}. Maybe one wasn't destroyed on scene load?! Check for 'DontDestroyOnLoad'. Destroying old entry, adding new.", netView.ViewID, netView, listedView));
  873. }
  874. else
  875. {
  876. return;
  877. }
  878. RemoveInstantiatedGO(listedView.gameObject, true);
  879. }
  880. // Debug.Log("adding view to known list: " + netView);
  881. photonViewList.Add(netView.ViewID, netView);
  882. netView.removedFromLocalViewList = false;
  883. //Debug.LogError("view being added. " + netView); // Exit Games internal log
  884. if (PhotonNetwork.LogLevel >= PunLogLevel.Full)
  885. {
  886. Debug.Log("Registered PhotonView: " + netView.ViewID);
  887. }
  888. }
  889. /// <summary>
  890. /// Removes the RPCs of someone else (to be used as master).
  891. /// This won't clean any local caches. It just tells the server to forget a player's RPCs and instantiates.
  892. /// </summary>
  893. /// <param name="actorNumber"></param>
  894. public static void OpCleanActorRpcBuffer(int actorNumber)
  895. {
  896. RaiseEventOptions options = new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache, TargetActors = new int[] { actorNumber } };
  897. PhotonNetwork.RaiseEventInternal(PunEvent.RPC, null, options, SendOptions.SendReliable);
  898. }
  899. /// <summary>
  900. /// Instead removing RPCs or Instantiates, this removed everything cached by the actor.
  901. /// </summary>
  902. /// <param name="actorNumber"></param>
  903. public static void OpRemoveCompleteCacheOfPlayer(int actorNumber)
  904. {
  905. RaiseEventOptions options = new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache, TargetActors = new int[] { actorNumber } };
  906. PhotonNetwork.RaiseEventInternal(0, null, options, SendOptions.SendReliable);
  907. }
  908. public static void OpRemoveCompleteCache()
  909. {
  910. RaiseEventOptions options = new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache, Receivers = ReceiverGroup.MasterClient };
  911. PhotonNetwork.RaiseEventInternal(0, null, options, SendOptions.SendReliable);
  912. }
  913. /// This clears the cache of any player/actor who's no longer in the room (making it a simple clean-up option for a new master)
  914. private static void RemoveCacheOfLeftPlayers()
  915. {
  916. ParameterDictionary opParameters = new ParameterDictionary(2);
  917. opParameters[ParameterCode.Code] = (byte)0; // any event
  918. opParameters[ParameterCode.Cache] = (byte)EventCaching.RemoveFromRoomCacheForActorsLeft; // option to clear the room cache of all events of players who left
  919. NetworkingClient.LoadBalancingPeer.SendOperation((byte)OperationCode.RaiseEvent, opParameters, SendOptions.SendReliable); // TODO: Check if this is the best implementation possible
  920. }
  921. // Remove RPCs of view (if they are local player's RPCs)
  922. public static void CleanRpcBufferIfMine(PhotonView view)
  923. {
  924. if (view.OwnerActorNr != NetworkingClient.LocalPlayer.ActorNumber && !NetworkingClient.LocalPlayer.IsMasterClient)
  925. {
  926. Debug.LogError("Cannot remove cached RPCs on a PhotonView thats not ours! " + view.Owner + " scene: " + view.IsRoomView);
  927. return;
  928. }
  929. OpCleanRpcBuffer(view);
  930. }
  931. private static readonly Hashtable rpcFilterByViewId = new ExitGames.Client.Photon.Hashtable();
  932. private static readonly RaiseEventOptions OpCleanRpcBufferOptions = new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache };
  933. /// <summary>Cleans server RPCs for PhotonView (without any further checks).</summary>
  934. public static void OpCleanRpcBuffer(PhotonView view)
  935. {
  936. rpcFilterByViewId[keyByteZero] = view.ViewID;
  937. PhotonNetwork.RaiseEventInternal(PunEvent.RPC, rpcFilterByViewId, OpCleanRpcBufferOptions, SendOptions.SendReliable);
  938. }
  939. /// <summary>
  940. /// Remove all buffered RPCs from server that were sent in the targetGroup, if this is the Master Client or if this controls the individual PhotonView.
  941. /// </summary>
  942. /// <remarks>
  943. /// This method requires either:
  944. /// - This client is the Master Client (can remove any RPCs per group).
  945. /// - Any other client: each PhotonView is checked if it is under this client's control. Only those RPCs are removed.
  946. /// </remarks>
  947. /// <param name="group">Interest group that gets all RPCs removed.</param>
  948. public static void RemoveRPCsInGroup(int group)
  949. {
  950. foreach (PhotonView view in photonViewList.Values)
  951. {
  952. if (view.Group == group)
  953. {
  954. CleanRpcBufferIfMine(view);
  955. }
  956. }
  957. }
  958. /// <summary>
  959. /// Clear buffered RPCs based on filter parameters.
  960. /// </summary>
  961. /// <param name="viewId">The viewID of the PhotonView where the RPC has been called on. We actually need its ViewID. If 0 (default) is provided, all PhotonViews/ViewIDs are considered.</param>
  962. /// <param name="methodName">The RPC method name, if possible we will use its hash shortcut for efficiency. If none (null or empty string) is provided all RPC method names are considered.</param>
  963. /// <param name="callersActorNumbers">The actor numbers of the players who called/buffered the RPC. For example if two players buffered the same RPC you can clear the buffered RPC of one and keep the other. If none (null or empty array) is provided all senders are considered.</param>
  964. /// <returns>If the operation could be sent to the server.</returns>
  965. public static bool RemoveBufferedRPCs(int viewId = 0, string methodName = null, int[] callersActorNumbers = null/*, params object[] parameters*/)
  966. {
  967. Hashtable filter = new Hashtable(2);
  968. if (viewId != 0)
  969. {
  970. filter[keyByteZero] = viewId;
  971. }
  972. if (!string.IsNullOrEmpty(methodName))
  973. {
  974. // send name or shortcut (if available)
  975. int shortcut;
  976. if (rpcShortcuts.TryGetValue(methodName, out shortcut))
  977. {
  978. filter[keyByteFive] = (byte)shortcut; // LIMITS RPC COUNT
  979. }
  980. else
  981. {
  982. filter[keyByteThree] = methodName;
  983. }
  984. }
  985. //if (parameters != null && parameters.Length > 0)
  986. //{
  987. // filter[keyByteFour] = parameters;
  988. //}
  989. RaiseEventOptions raiseEventOptions = new RaiseEventOptions();
  990. raiseEventOptions.CachingOption = EventCaching.RemoveFromRoomCache;
  991. if (callersActorNumbers != null)
  992. {
  993. raiseEventOptions.TargetActors = callersActorNumbers;
  994. }
  995. return RaiseEventInternal(PunEvent.RPC, filter, raiseEventOptions, SendOptions.SendReliable);
  996. }
  997. /// <summary>
  998. /// Sets level prefix for PhotonViews instantiated later on. Don't set it if you need only one!
  999. /// </summary>
  1000. /// <remarks>
  1001. /// Important: If you don't use multiple level prefixes, simply don't set this value. The
  1002. /// default value is optimized out of the traffic.
  1003. ///
  1004. /// This won't affect existing PhotonViews (they can't be changed yet for existing PhotonViews).
  1005. ///
  1006. /// Messages sent with a different level prefix will be received but not executed. This affects
  1007. /// RPCs, Instantiates and synchronization.
  1008. ///
  1009. /// Be aware that PUN never resets this value, you'll have to do so yourself.
  1010. /// </remarks>
  1011. /// <param name="prefix">Max value is short.MaxValue = 255</param>
  1012. public static void SetLevelPrefix(byte prefix)
  1013. {
  1014. // TODO: check can use network
  1015. currentLevelPrefix = prefix;
  1016. // TODO: should we really change the prefix for existing PVs?! better keep it!
  1017. //foreach (PhotonView view in photonViewList.Values)
  1018. //{
  1019. // view.prefix = prefix;
  1020. //}
  1021. }
  1022. /// RPC Hashtable Structure
  1023. /// (byte)0 -> (int) ViewId (combined from actorNr and actor-unique-id)
  1024. /// (byte)1 -> (short) prefix (level)
  1025. /// (byte)2 -> (int) server timestamp
  1026. /// (byte)3 -> (string) methodname
  1027. /// (byte)4 -> (object[]) parameters
  1028. /// (byte)5 -> (byte) method shortcut (alternative to name)
  1029. ///
  1030. /// This is sent as event (code: 200) which will contain a sender (origin of this RPC).
  1031. static ExitGames.Client.Photon.Hashtable rpcEvent = new ExitGames.Client.Photon.Hashtable();
  1032. static RaiseEventOptions RpcOptionsToAll = new RaiseEventOptions();
  1033. internal static void RPC(PhotonView view, string methodName, RpcTarget target, Player player, bool encrypt, params object[] parameters)
  1034. {
  1035. if (blockedSendingGroups.Contains(view.Group))
  1036. {
  1037. return; // Block sending on this group
  1038. }
  1039. if (view.ViewID < 1)
  1040. {
  1041. Debug.LogError("Illegal view ID:" + view.ViewID + " method: " + methodName + " GO:" + view.gameObject.name);
  1042. }
  1043. if (PhotonNetwork.LogLevel >= PunLogLevel.Full)
  1044. {
  1045. Debug.Log("Sending RPC \"" + methodName + "\" to target: " + target + " or player:" + player + ".");
  1046. }
  1047. //ts: changed RPCs to a one-level hashtable as described in internal.txt
  1048. rpcEvent.Clear();
  1049. rpcEvent[keyByteZero] = (int)view.ViewID; // LIMITS NETWORKVIEWS&PLAYERS
  1050. if (view.Prefix > 0)
  1051. {
  1052. rpcEvent[keyByteOne] = (short)view.Prefix;
  1053. }
  1054. rpcEvent[keyByteTwo] = PhotonNetwork.ServerTimestamp;
  1055. // send name or shortcut (if available)
  1056. int shortcut = 0;
  1057. if (rpcShortcuts.TryGetValue(methodName, out shortcut))
  1058. {
  1059. rpcEvent[keyByteFive] = (byte)shortcut; // LIMITS RPC COUNT
  1060. }
  1061. else
  1062. {
  1063. rpcEvent[keyByteThree] = methodName;
  1064. }
  1065. if (parameters != null && parameters.Length > 0)
  1066. {
  1067. rpcEvent[keyByteFour] = (object[])parameters;
  1068. }
  1069. SendOptions sendOptions = new SendOptions() { Reliability = true, Encrypt = encrypt };
  1070. // if sent to target player, this overrides the target
  1071. if (player != null)
  1072. {
  1073. if (NetworkingClient.LocalPlayer.ActorNumber == player.ActorNumber)
  1074. {
  1075. ExecuteRpc(rpcEvent, player);
  1076. }
  1077. else
  1078. {
  1079. RaiseEventOptions options = new RaiseEventOptions() { TargetActors = new int[] { player.ActorNumber } };
  1080. PhotonNetwork.RaiseEventInternal(PunEvent.RPC, rpcEvent, options, sendOptions);
  1081. // NetworkingClient.OpRaiseEvent(PunEvent.RPC, rpcEvent, options, new SendOptions() { Reliability = true, Encrypt = encrypt });
  1082. }
  1083. return;
  1084. }
  1085. switch (target)
  1086. {
  1087. // send to a specific set of players
  1088. case RpcTarget.All:
  1089. RpcOptionsToAll.InterestGroup = (byte)view.Group; // NOTE: Test-wise, this is static and re-used to avoid memory garbage
  1090. PhotonNetwork.RaiseEventInternal(PunEvent.RPC, rpcEvent, RpcOptionsToAll, sendOptions);
  1091. // Execute local
  1092. ExecuteRpc(rpcEvent, NetworkingClient.LocalPlayer);
  1093. break;
  1094. case RpcTarget.Others:
  1095. {
  1096. RaiseEventOptions options = new RaiseEventOptions() { InterestGroup = (byte)view.Group };
  1097. PhotonNetwork.RaiseEventInternal(PunEvent.RPC, rpcEvent, options, sendOptions);
  1098. break;
  1099. }
  1100. case RpcTarget.AllBuffered:
  1101. {
  1102. RaiseEventOptions options = new RaiseEventOptions() { CachingOption = EventCaching.AddToRoomCache };
  1103. PhotonNetwork.RaiseEventInternal(PunEvent.RPC, rpcEvent, options, sendOptions);
  1104. // Execute local
  1105. ExecuteRpc(rpcEvent, NetworkingClient.LocalPlayer);
  1106. break;
  1107. }
  1108. case RpcTarget.OthersBuffered:
  1109. {
  1110. RaiseEventOptions options = new RaiseEventOptions() { CachingOption = EventCaching.AddToRoomCache };
  1111. PhotonNetwork.RaiseEventInternal(PunEvent.RPC, rpcEvent, options, sendOptions);
  1112. break;
  1113. }
  1114. case RpcTarget.MasterClient:
  1115. {
  1116. if (NetworkingClient.LocalPlayer.IsMasterClient)
  1117. {
  1118. ExecuteRpc(rpcEvent, NetworkingClient.LocalPlayer);
  1119. }
  1120. else
  1121. {
  1122. RaiseEventOptions options = new RaiseEventOptions() { Receivers = ReceiverGroup.MasterClient };
  1123. PhotonNetwork.RaiseEventInternal(PunEvent.RPC, rpcEvent, options, sendOptions);
  1124. }
  1125. break;
  1126. }
  1127. case RpcTarget.AllViaServer:
  1128. {
  1129. RaiseEventOptions options = new RaiseEventOptions() { InterestGroup = (byte)view.Group, Receivers = ReceiverGroup.All };
  1130. PhotonNetwork.RaiseEventInternal(PunEvent.RPC, rpcEvent, options, sendOptions);
  1131. if (PhotonNetwork.OfflineMode)
  1132. {
  1133. ExecuteRpc(rpcEvent, NetworkingClient.LocalPlayer);
  1134. }
  1135. break;
  1136. }
  1137. case RpcTarget.AllBufferedViaServer:
  1138. {
  1139. RaiseEventOptions options = new RaiseEventOptions() { InterestGroup = (byte)view.Group, Receivers = ReceiverGroup.All, CachingOption = EventCaching.AddToRoomCache };
  1140. PhotonNetwork.RaiseEventInternal(PunEvent.RPC, rpcEvent, options, sendOptions);
  1141. if (PhotonNetwork.OfflineMode)
  1142. {
  1143. ExecuteRpc(rpcEvent, NetworkingClient.LocalPlayer);
  1144. }
  1145. break;
  1146. }
  1147. default:
  1148. Debug.LogError("Unsupported target enum: " + target);
  1149. break;
  1150. }
  1151. }
  1152. /// <summary>Enable/disable receiving on given Interest Groups (applied to PhotonViews).</summary>
  1153. /// <remarks>
  1154. /// A client can tell the server which Interest Groups it's interested in.
  1155. /// The server will only forward events for those Interest Groups to that client (saving bandwidth and performance).
  1156. ///
  1157. /// See: https://doc.photonengine.com/en-us/pun/v2/gameplay/interestgroups
  1158. ///
  1159. /// See: https://doc.photonengine.com/en-us/pun/v2/demos-and-tutorials/package-demos/culling-demo
  1160. /// </remarks>
  1161. /// <param name="disableGroups">The interest groups to disable (or null).</param>
  1162. /// <param name="enableGroups">The interest groups to enable (or null).</param>
  1163. public static void SetInterestGroups(byte[] disableGroups, byte[] enableGroups)
  1164. {
  1165. // TODO: check can use network
  1166. if (disableGroups != null)
  1167. {
  1168. if (disableGroups.Length == 0)
  1169. {
  1170. // a byte[0] should disable ALL groups in one step and before any groups are enabled. we do this locally, too.
  1171. allowedReceivingGroups.Clear();
  1172. }
  1173. else
  1174. {
  1175. for (int index = 0; index < disableGroups.Length; index++)
  1176. {
  1177. byte g = disableGroups[index];
  1178. if (g <= 0)
  1179. {
  1180. Debug.LogError("Error: PhotonNetwork.SetInterestGroups was called with an illegal group number: " + g + ". The Group number should be at least 1.");
  1181. continue;
  1182. }
  1183. if (allowedReceivingGroups.Contains(g))
  1184. {
  1185. allowedReceivingGroups.Remove(g);
  1186. }
  1187. }
  1188. }
  1189. }
  1190. if (enableGroups != null)
  1191. {
  1192. if (enableGroups.Length == 0)
  1193. {
  1194. // a byte[0] should enable ALL groups in one step. we do this locally, too.
  1195. for (byte index = 0; index < byte.MaxValue; index++)
  1196. {
  1197. allowedReceivingGroups.Add(index);
  1198. }
  1199. allowedReceivingGroups.Add(byte.MaxValue);
  1200. }
  1201. else
  1202. {
  1203. for (int index = 0; index < enableGroups.Length; index++)
  1204. {
  1205. byte g = enableGroups[index];
  1206. if (g <= 0)
  1207. {
  1208. Debug.LogError("Error: PhotonNetwork.SetInterestGroups was called with an illegal group number: " + g + ". The Group number should be at least 1.");
  1209. continue;
  1210. }
  1211. allowedReceivingGroups.Add(g);
  1212. }
  1213. }
  1214. }
  1215. if (!PhotonNetwork.offlineMode)
  1216. {
  1217. NetworkingClient.OpChangeGroups(disableGroups, enableGroups);
  1218. }
  1219. }
  1220. /// <summary>Enable/disable sending on given group (applied to PhotonViews)</summary>
  1221. /// <remarks>
  1222. /// This does not interact with the Photon server-side.
  1223. /// It's just a client-side setting to suppress updates, should they be sent to one of the blocked groups.
  1224. ///
  1225. /// This setting is not particularly useful, as it means that updates literally never reach the server or anyone else.
  1226. /// Use with care.
  1227. /// </remarks>
  1228. /// <param name="group">The interest group to affect.</param>
  1229. /// <param name="enabled">Sets if sending to group is enabled (or not).</param>
  1230. public static void SetSendingEnabled(byte group, bool enabled)
  1231. {
  1232. // TODO: check can use network
  1233. if (!enabled)
  1234. {
  1235. blockedSendingGroups.Add(group); // can be added to HashSet no matter if already in it
  1236. }
  1237. else
  1238. {
  1239. blockedSendingGroups.Remove(group);
  1240. }
  1241. }
  1242. /// <summary>Enable/disable sending on given groups (applied to PhotonViews)</summary>
  1243. /// <remarks>
  1244. /// This does not interact with the Photon server-side.
  1245. /// It's just a client-side setting to suppress updates, should they be sent to one of the blocked groups.
  1246. ///
  1247. /// This setting is not particularly useful, as it means that updates literally never reach the server or anyone else.
  1248. /// Use with care.
  1249. /// <param name="enableGroups">The interest groups to enable sending on (or null).</param>
  1250. /// <param name="disableGroups">The interest groups to disable sending on (or null).</param>
  1251. public static void SetSendingEnabled(byte[] disableGroups, byte[] enableGroups)
  1252. {
  1253. // TODO: check can use network
  1254. if (disableGroups != null)
  1255. {
  1256. for (int index = 0; index < disableGroups.Length; index++)
  1257. {
  1258. byte g = disableGroups[index];
  1259. blockedSendingGroups.Add(g);
  1260. }
  1261. }
  1262. if (enableGroups != null)
  1263. {
  1264. for (int index = 0; index < enableGroups.Length; index++)
  1265. {
  1266. byte g = enableGroups[index];
  1267. blockedSendingGroups.Remove(g);
  1268. }
  1269. }
  1270. }
  1271. internal static void NewSceneLoaded()
  1272. {
  1273. if (loadingLevelAndPausedNetwork)
  1274. {
  1275. _AsyncLevelLoadingOperation = null;
  1276. loadingLevelAndPausedNetwork = false;
  1277. PhotonNetwork.IsMessageQueueRunning = true;
  1278. }
  1279. else
  1280. {
  1281. PhotonNetwork.SetLevelInPropsIfSynced(SceneManagerHelper.ActiveSceneName);
  1282. }
  1283. // Debug.Log("OnLevelWasLoaded photonViewList.Count: " + photonViewList.Count); // Exit Games internal log
  1284. List<int> removeKeys = new List<int>();
  1285. foreach (KeyValuePair<int, PhotonView> kvp in photonViewList)
  1286. {
  1287. PhotonView view = kvp.Value;
  1288. if (view == null)
  1289. {
  1290. removeKeys.Add(kvp.Key);
  1291. }
  1292. }
  1293. for (int index = 0; index < removeKeys.Count; index++)
  1294. {
  1295. int key = removeKeys[index];
  1296. photonViewList.Remove(key);
  1297. }
  1298. if (removeKeys.Count > 0)
  1299. {
  1300. if (PhotonNetwork.LogLevel >= PunLogLevel.Informational)
  1301. Debug.Log("New level loaded. Removed " + removeKeys.Count + " scene view IDs from last level.");
  1302. }
  1303. }
  1304. /// <summary>
  1305. /// Defines how many updated produced by OnPhotonSerialize() are batched into one message.
  1306. /// </summary>
  1307. /// <remarks>
  1308. /// A low number increases overhead, a high number might lead to fragmented messages.
  1309. /// </remarks>
  1310. public static int ObjectsInOneUpdate = 20;
  1311. private static readonly PhotonStream serializeStreamOut = new PhotonStream(true, null);
  1312. private static readonly PhotonStream serializeStreamIn = new PhotonStream(false, null);
  1313. ///<summary> cache the RaiseEventOptions to prevent redundant Memory Allocation</summary>
  1314. private static RaiseEventOptions serializeRaiseEvOptions = new RaiseEventOptions();
  1315. private struct RaiseEventBatch : IEquatable<RaiseEventBatch>
  1316. {
  1317. public byte Group;
  1318. public bool Reliable;
  1319. public override int GetHashCode()
  1320. {
  1321. return (this.Group << 1) + (this.Reliable ? 1 : 0);
  1322. }
  1323. public bool Equals(RaiseEventBatch other)
  1324. {
  1325. return this.Reliable == other.Reliable && this.Group == other.Group;
  1326. }
  1327. }
  1328. private class SerializeViewBatch : IEquatable<SerializeViewBatch>, IEquatable<RaiseEventBatch>
  1329. {
  1330. public readonly RaiseEventBatch Batch;
  1331. public List<object> ObjectUpdates;
  1332. private int defaultSize = PhotonNetwork.ObjectsInOneUpdate;
  1333. private int offset;
  1334. // the offset enables us to skip the first X entries in the ObjectUpdate(s), leaving room for (e.g.) timestamp of sending and level prefix
  1335. public SerializeViewBatch(RaiseEventBatch batch, int offset)
  1336. {
  1337. this.Batch = batch;
  1338. this.ObjectUpdates = new List<object>(this.defaultSize);
  1339. this.offset = offset;
  1340. for (int i = 0; i < offset; i++) this.ObjectUpdates.Add(null);
  1341. }
  1342. public override int GetHashCode()
  1343. {
  1344. return (this.Batch.Group << 1) + (this.Batch.Reliable ? 1 : 0);
  1345. }
  1346. public bool Equals(SerializeViewBatch other)
  1347. {
  1348. return this.Equals(other.Batch);
  1349. }
  1350. public bool Equals(RaiseEventBatch other)
  1351. {
  1352. return this.Batch.Reliable == other.Reliable && this.Batch.Group == other.Group;
  1353. }
  1354. public override bool Equals(object obj)
  1355. {
  1356. SerializeViewBatch other = obj as SerializeViewBatch;
  1357. return other != null && this.Batch.Equals(other.Batch);
  1358. }
  1359. public void Clear()
  1360. {
  1361. this.ObjectUpdates.Clear();
  1362. for (int i = 0; i < offset; i++) this.ObjectUpdates.Add(null);
  1363. }
  1364. public void Add(List<object> viewData)
  1365. {
  1366. if (this.ObjectUpdates.Count >= this.ObjectUpdates.Capacity)
  1367. {
  1368. // NOTE: we could also trim to new size
  1369. throw new Exception("Can't add. Size exceeded.");
  1370. }
  1371. this.ObjectUpdates.Add(viewData);
  1372. }
  1373. }
  1374. private static readonly Dictionary<RaiseEventBatch, SerializeViewBatch> serializeViewBatches = new Dictionary<RaiseEventBatch, SerializeViewBatch>();
  1375. /// <summary>Calls all locally controlled PhotonViews to write their updates in OnPhotonSerializeView. Called by a PhotonHandler.</summary>
  1376. internal static void RunViewUpdate()
  1377. {
  1378. if (PhotonNetwork.OfflineMode || CurrentRoom == null || CurrentRoom.Players == null)
  1379. {
  1380. return;
  1381. }
  1382. // no need to send OnSerialize messages while being alone (these are not buffered anyway)
  1383. #if !PHOTON_DEVELOP
  1384. if (CurrentRoom.Players.Count <= 1)
  1385. {
  1386. return;
  1387. }
  1388. #else
  1389. serializeRaiseEvOptions.Receivers = (CurrentRoom.Players.Count == 1) ? ReceiverGroup.All : ReceiverGroup.Others;
  1390. #endif
  1391. /* Format of the event's data object[]:
  1392. * [0] = PhotonNetwork.ServerTimestamp;
  1393. * [1] = currentLevelPrefix; OPTIONAL!
  1394. * [2] = object[] of PhotonView x
  1395. * [3] = object[] of PhotonView y or NULL
  1396. * [...]
  1397. *
  1398. * We only combine updates for XY objects into one RaiseEvent to avoid fragmentation.
  1399. * The Reliability and Interest Group are only used for RaiseEvent and not contained in the event/data that reaches the other clients.
  1400. * This is read in OnEvent().
  1401. */
  1402. var enumerator = photonViewList.GetEnumerator(); // replacing foreach (PhotonView view in this.photonViewList.Values) for memory allocation improvement
  1403. while (enumerator.MoveNext())
  1404. {
  1405. PhotonView view = enumerator.Current.Value;
  1406. // a client only sends updates for active, synchronized PhotonViews that are under it's control (isMine)
  1407. if (view.Synchronization == ViewSynchronization.Off || view.IsMine == false || view.isActiveAndEnabled == false)
  1408. {
  1409. continue;
  1410. }
  1411. if (blockedSendingGroups.Contains(view.Group))
  1412. {
  1413. continue; // Block sending on this group
  1414. }
  1415. // call the PhotonView's serialize method(s)
  1416. List<object> evData = OnSerializeWrite(view);
  1417. if (evData == null)
  1418. {
  1419. continue;
  1420. }
  1421. RaiseEventBatch eventBatch = new RaiseEventBatch();
  1422. eventBatch.Reliable = view.Synchronization == ViewSynchronization.ReliableDeltaCompressed || view.mixedModeIsReliable;
  1423. eventBatch.Group = view.Group;
  1424. SerializeViewBatch svBatch = null;
  1425. bool found = serializeViewBatches.TryGetValue(eventBatch, out svBatch);
  1426. if (!found)
  1427. {
  1428. svBatch = new SerializeViewBatch(eventBatch, 2); // NOTE: the 2 first entries are kept empty for timestamp and level prefix
  1429. serializeViewBatches.Add(eventBatch, svBatch);
  1430. }
  1431. svBatch.Add(evData);
  1432. if (svBatch.ObjectUpdates.Count == svBatch.ObjectUpdates.Capacity)
  1433. {
  1434. SendSerializeViewBatch(svBatch);
  1435. }
  1436. }
  1437. var enumeratorB = serializeViewBatches.GetEnumerator();
  1438. while (enumeratorB.MoveNext())
  1439. {
  1440. SendSerializeViewBatch(enumeratorB.Current.Value);
  1441. }
  1442. }
  1443. private static void SendSerializeViewBatch(SerializeViewBatch batch)
  1444. {
  1445. if (batch == null || batch.ObjectUpdates.Count <= 2)
  1446. {
  1447. return;
  1448. }
  1449. serializeRaiseEvOptions.InterestGroup = batch.Batch.Group;
  1450. batch.ObjectUpdates[0] = PhotonNetwork.ServerTimestamp;
  1451. batch.ObjectUpdates[1] = (currentLevelPrefix != 0) ? (object)currentLevelPrefix : null;
  1452. byte code = batch.Batch.Reliable ? PunEvent.SendSerializeReliable : PunEvent.SendSerialize;
  1453. PhotonNetwork.RaiseEventInternal(code, batch.ObjectUpdates, serializeRaiseEvOptions, batch.Batch.Reliable ? SendOptions.SendReliable : SendOptions.SendUnreliable);
  1454. batch.Clear();
  1455. }
  1456. // calls OnPhotonSerializeView (through ExecuteOnSerialize)
  1457. // the content created here is consumed by receivers in: ReadOnSerialize
  1458. private static List<object> OnSerializeWrite(PhotonView view)
  1459. {
  1460. if (view.Synchronization == ViewSynchronization.Off)
  1461. {
  1462. return null;
  1463. }
  1464. // each view creates a list of values that should be sent
  1465. PhotonMessageInfo info = new PhotonMessageInfo(NetworkingClient.LocalPlayer, PhotonNetwork.ServerTimestamp, view);
  1466. if (view.syncValues == null) view.syncValues = new List<object>();
  1467. view.syncValues.Clear();
  1468. serializeStreamOut.SetWriteStream(view.syncValues);
  1469. serializeStreamOut.SendNext(null); //to become: viewID,
  1470. serializeStreamOut.SendNext(null); //to become: is compressed
  1471. serializeStreamOut.SendNext(null); //to become: null-values (for compression) followed by: values for this object's update
  1472. view.SerializeView(serializeStreamOut, info);
  1473. // check if there are actual values to be sent (after the "header" of viewId, (bool)compressed and (int[])nullValues)
  1474. if (serializeStreamOut.Count <= SyncFirstValue)
  1475. {
  1476. return null;
  1477. }
  1478. List<object> currentValues = serializeStreamOut.GetWriteStream();
  1479. currentValues[SyncViewId] = view.ViewID;
  1480. currentValues[SyncCompressed] = false; // (bool) compression was used.
  1481. currentValues[SyncNullValues] = null; // if reliable compressed, this is non-null.
  1482. // next: sequence of values in this object's update.
  1483. if (view.Synchronization == ViewSynchronization.Unreliable)
  1484. {
  1485. return currentValues;
  1486. }
  1487. // ViewSynchronization: Off, Unreliable, UnreliableOnChange, ReliableDeltaCompressed
  1488. if (view.Synchronization == ViewSynchronization.UnreliableOnChange)
  1489. {
  1490. if (AlmostEquals(currentValues, view.lastOnSerializeDataSent))
  1491. {
  1492. if (view.mixedModeIsReliable)
  1493. {
  1494. return null;
  1495. }
  1496. view.mixedModeIsReliable = true;
  1497. List<object> temp = view.lastOnSerializeDataSent; // TODO: extract "exchange" into method in PV
  1498. view.lastOnSerializeDataSent = currentValues;
  1499. view.syncValues = temp;
  1500. }
  1501. else
  1502. {
  1503. view.mixedModeIsReliable = false;
  1504. List<object> temp = view.lastOnSerializeDataSent; // TODO: extract "exchange" into method in PV
  1505. view.lastOnSerializeDataSent = currentValues;
  1506. view.syncValues = temp;
  1507. }
  1508. return currentValues;
  1509. }
  1510. if (view.Synchronization == ViewSynchronization.ReliableDeltaCompressed)
  1511. {
  1512. // TODO: fix delta compression / comparison
  1513. // compress content of data set (by comparing to view.lastOnSerializeDataSent)
  1514. // the "original" dataArray is NOT modified by DeltaCompressionWrite
  1515. List<object> dataToSend = DeltaCompressionWrite(view.lastOnSerializeDataSent, currentValues);
  1516. // cache the values that were written this time (not the compressed values)
  1517. List<object> temp = view.lastOnSerializeDataSent; // TODO: extract "exchange" into method in PV
  1518. view.lastOnSerializeDataSent = currentValues;
  1519. view.syncValues = temp;
  1520. return dataToSend;
  1521. }
  1522. return null;
  1523. }
  1524. /// <summary>
  1525. /// Reads updates created by OnSerializeWrite
  1526. /// </summary>
  1527. private static void OnSerializeRead(object[] data, Player sender, int networkTime, short correctPrefix)
  1528. {
  1529. // read view ID from key (byte)0: a int-array (PUN 1.17++)
  1530. int viewID = (int)data[SyncViewId];
  1531. // debug:
  1532. //LogObjectArray(data);
  1533. PhotonView view = GetPhotonView(viewID);
  1534. if (view == null)
  1535. {
  1536. Debug.LogWarning("Received OnSerialization for view ID " + viewID + ". We have no such PhotonView! Ignore this if you're joining or leaving a room. State: " + NetworkingClient.State);
  1537. return;
  1538. }
  1539. if (view.Prefix > 0 && correctPrefix != view.Prefix)
  1540. {
  1541. Debug.LogError("Received OnSerialization for view ID " + viewID + " with prefix " + correctPrefix + ". Our prefix is " + view.Prefix);
  1542. return;
  1543. }
  1544. // SetReceiving filtering
  1545. if (view.Group != 0 && !allowedReceivingGroups.Contains(view.Group))
  1546. {
  1547. return; // Ignore group
  1548. }
  1549. if (view.Synchronization == ViewSynchronization.ReliableDeltaCompressed)
  1550. {
  1551. object[] uncompressed = DeltaCompressionRead(view.lastOnSerializeDataReceived, data);
  1552. //LogObjectArray(uncompressed,"uncompressed ");
  1553. if (uncompressed == null)
  1554. {
  1555. // Skip this packet as we haven't got received complete-copy of this view yet.
  1556. if (PhotonNetwork.LogLevel >= PunLogLevel.Informational)
  1557. {
  1558. Debug.Log("Skipping packet for " + view.name + " [" + view.ViewID +
  1559. "] as we haven't received a full packet for delta compression yet. This is OK if it happens for the first few frames after joining a game.");
  1560. }
  1561. return;
  1562. }
  1563. // store last received values (uncompressed) for delta-compression usage
  1564. view.lastOnSerializeDataReceived = uncompressed;
  1565. data = uncompressed;
  1566. }
  1567. // TODO: re-check if ownership needs to be adjusted based on updates.
  1568. // most likely, only the PhotonView.Controller should be affected, if anything at all.
  1569. // TODO: find a way to sync the owner of a PV for late joiners.
  1570. //// This is when joining late to assign ownership to the sender
  1571. //// this has nothing to do with reading the actual synchronization update.
  1572. //// We don't do anything if OwnerShip Was Touched, which means we got the infos already. We only possibly act if ownership was never transfered.
  1573. //// We do override OwnershipWasTransfered if owner is the masterClient.
  1574. //if (sender.ID != view.OwnerActorNr && (!view.OwnershipWasTransfered || view.OwnerActorNr == 0) && view.currentMasterID == -1)
  1575. //{
  1576. // // obviously the owner changed and we didn't yet notice.
  1577. // //Debug.Log("Adjusting owner to sender of updates. From: " + view.OwnerActorNr + " to: " + sender.ID);
  1578. // view.OwnerActorNr = sender.ID;
  1579. //}
  1580. serializeStreamIn.SetReadStream(data, 3);
  1581. PhotonMessageInfo info = new PhotonMessageInfo(sender, networkTime, view);
  1582. view.DeserializeView(serializeStreamIn, info);
  1583. }
  1584. // compresses currentContent by using NULL as value if currentContent equals previousContent
  1585. // skips initial indexes, as defined by SyncFirstValue
  1586. // to conserve memory, the previousContent is re-used as buffer for the result! duplicate the values before using this, if needed
  1587. // returns null, if nothing must be sent (current content might be null, which also returns null)
  1588. // SyncFirstValue should be the index of the first actual data-value (3 in PUN's case, as 0=viewId, 1=(bool)compressed, 2=(int[])values that are now null)
  1589. public const int SyncViewId = 0;
  1590. public const int SyncCompressed = 1;
  1591. public const int SyncNullValues = 2;
  1592. public const int SyncFirstValue = 3;
  1593. private static List<object> DeltaCompressionWrite(List<object> previousContent, List<object> currentContent)
  1594. {
  1595. if (currentContent == null || previousContent == null || previousContent.Count != currentContent.Count)
  1596. {
  1597. return currentContent; // the current data needs to be sent (which might be null)
  1598. }
  1599. if (currentContent.Count <= SyncFirstValue)
  1600. {
  1601. return null; // this send doesn't contain values (except the "headers"), so it's not being sent
  1602. }
  1603. List<object> compressedContent = previousContent; // the previous content is no longer needed, once we compared the values!
  1604. compressedContent[SyncCompressed] = false;
  1605. int compressedValues = 0;
  1606. Queue<int> valuesThatAreChangedToNull = null;
  1607. for (int index = SyncFirstValue; index < currentContent.Count; index++)
  1608. {
  1609. object newObj = currentContent[index];
  1610. object oldObj = previousContent[index];
  1611. if (AlmostEquals(newObj, oldObj))
  1612. {
  1613. // compress (by using null, instead of value, which is same as before)
  1614. compressedValues++;
  1615. compressedContent[index] = null;
  1616. }
  1617. else
  1618. {
  1619. compressedContent[index] = newObj;
  1620. // value changed, we don't replace it with null
  1621. // new value is null (like a compressed value): we have to mark it so it STAYS null instead of being replaced with previous value
  1622. if (newObj == null)
  1623. {
  1624. if (valuesThatAreChangedToNull == null)
  1625. {
  1626. valuesThatAreChangedToNull = new Queue<int>(currentContent.Count);
  1627. }
  1628. valuesThatAreChangedToNull.Enqueue(index);
  1629. }
  1630. }
  1631. }
  1632. // Only send the list of compressed fields if we actually compressed 1 or more fields.
  1633. if (compressedValues > 0)
  1634. {
  1635. if (compressedValues == currentContent.Count - SyncFirstValue)
  1636. {
  1637. // all values are compressed to null, we have nothing to send
  1638. return null;
  1639. }
  1640. compressedContent[SyncCompressed] = true;
  1641. if (valuesThatAreChangedToNull != null)
  1642. {
  1643. compressedContent[SyncNullValues] = valuesThatAreChangedToNull.ToArray(); // data that is actually null (not just cause we didn't want to send it)
  1644. }
  1645. }
  1646. compressedContent[SyncViewId] = currentContent[SyncViewId];
  1647. return compressedContent; // some data was compressed but we need to send something
  1648. }
  1649. private static object[] DeltaCompressionRead(object[] lastOnSerializeDataReceived, object[] incomingData)
  1650. {
  1651. if ((bool)incomingData[SyncCompressed] == false)
  1652. {
  1653. // index 1 marks "compressed" as being true.
  1654. return incomingData;
  1655. }
  1656. // Compression was applied (as data[1] == true)
  1657. // we need a previous "full" list of values to restore values that are null in this msg. else, ignore this
  1658. if (lastOnSerializeDataReceived == null)
  1659. {
  1660. return null;
  1661. }
  1662. int[] indexesThatAreChangedToNull = incomingData[2] as int[];
  1663. for (int index = SyncFirstValue; index < incomingData.Length; index++)
  1664. {
  1665. if (indexesThatAreChangedToNull != null && indexesThatAreChangedToNull.Contains(index))
  1666. {
  1667. continue; // if a value was set to null in this update, we don't need to fetch it from an earlier update
  1668. }
  1669. if (incomingData[index] == null)
  1670. {
  1671. // we replace null values in this received msg unless a index is in the "changed to null" list
  1672. object lastValue = lastOnSerializeDataReceived[index];
  1673. incomingData[index] = lastValue;
  1674. }
  1675. }
  1676. return incomingData;
  1677. }
  1678. // startIndex should be the index of the first actual data-value (3 in PUN's case, as 0=viewId, 1=(bool)compressed, 2=(int[])values that are now null)
  1679. // returns the incomingData with modified content. any object being null (means: value unchanged) gets replaced with a previously sent value. incomingData is being modified
  1680. private static bool AlmostEquals(IList<object> lastData, IList<object> currentContent)
  1681. {
  1682. if (lastData == null && currentContent == null)
  1683. {
  1684. return true;
  1685. }
  1686. if (lastData == null || currentContent == null || (lastData.Count != currentContent.Count))
  1687. {
  1688. return false;
  1689. }
  1690. for (int index = 0; index < currentContent.Count; index++)
  1691. {
  1692. object newObj = currentContent[index];
  1693. object oldObj = lastData[index];
  1694. if (!AlmostEquals(newObj, oldObj))
  1695. {
  1696. return false;
  1697. }
  1698. }
  1699. return true;
  1700. }
  1701. /// <summary>
  1702. /// Returns true if both objects are almost identical.
  1703. /// Used to check whether two objects are similar enough to skip an update.
  1704. /// </summary>
  1705. static bool AlmostEquals(object one, object two)
  1706. {
  1707. if (one == null || two == null)
  1708. {
  1709. return one == null && two == null;
  1710. }
  1711. if (!one.Equals(two))
  1712. {
  1713. // if A is not B, lets check if A is almost B
  1714. if (one is Vector3)
  1715. {
  1716. Vector3 a = (Vector3)one;
  1717. Vector3 b = (Vector3)two;
  1718. if (a.AlmostEquals(b, PhotonNetwork.PrecisionForVectorSynchronization))
  1719. {
  1720. return true;
  1721. }
  1722. }
  1723. else if (one is Vector2)
  1724. {
  1725. Vector2 a = (Vector2)one;
  1726. Vector2 b = (Vector2)two;
  1727. if (a.AlmostEquals(b, PhotonNetwork.PrecisionForVectorSynchronization))
  1728. {
  1729. return true;
  1730. }
  1731. }
  1732. else if (one is Quaternion)
  1733. {
  1734. Quaternion a = (Quaternion)one;
  1735. Quaternion b = (Quaternion)two;
  1736. if (a.AlmostEquals(b, PhotonNetwork.PrecisionForQuaternionSynchronization))
  1737. {
  1738. return true;
  1739. }
  1740. }
  1741. else if (one is float)
  1742. {
  1743. float a = (float)one;
  1744. float b = (float)two;
  1745. if (a.AlmostEquals(b, PhotonNetwork.PrecisionForFloatSynchronization))
  1746. {
  1747. return true;
  1748. }
  1749. }
  1750. // one does not equal two
  1751. return false;
  1752. }
  1753. return true;
  1754. }
  1755. // NOTE: Might be used as replacement for the equivalent method in SupportClass.
  1756. internal static bool GetMethod(MonoBehaviour monob, string methodType, out MethodInfo mi)
  1757. {
  1758. mi = null;
  1759. if (monob == null || string.IsNullOrEmpty(methodType))
  1760. {
  1761. return false;
  1762. }
  1763. List<MethodInfo> methods = SupportClassPun.GetMethods(monob.GetType(), null);
  1764. for (int index = 0; index < methods.Count; index++)
  1765. {
  1766. MethodInfo methodInfo = methods[index];
  1767. if (methodInfo.Name.Equals(methodType))
  1768. {
  1769. mi = methodInfo;
  1770. return true;
  1771. }
  1772. }
  1773. return false;
  1774. }
  1775. /// <summary>Internally used to detect the current scene and load it if PhotonNetwork.AutomaticallySyncScene is enabled.</summary>
  1776. internal static void LoadLevelIfSynced()
  1777. {
  1778. if (!PhotonNetwork.AutomaticallySyncScene || PhotonNetwork.IsMasterClient || PhotonNetwork.CurrentRoom == null)
  1779. {
  1780. return;
  1781. }
  1782. // check if "current level" is set in props
  1783. if (!PhotonNetwork.CurrentRoom.CustomProperties.ContainsKey(CurrentSceneProperty))
  1784. {
  1785. return;
  1786. }
  1787. // if loaded level is not the one defined by master in props, load that level
  1788. object sceneId = PhotonNetwork.CurrentRoom.CustomProperties[CurrentSceneProperty];
  1789. if (sceneId is int)
  1790. {
  1791. if (SceneManagerHelper.ActiveSceneBuildIndex != (int)sceneId)
  1792. {
  1793. PhotonNetwork.LoadLevel((int)sceneId);
  1794. }
  1795. }
  1796. else if (sceneId is string)
  1797. {
  1798. if (SceneManagerHelper.ActiveSceneName != (string)sceneId)
  1799. {
  1800. PhotonNetwork.LoadLevel((string)sceneId);
  1801. }
  1802. }
  1803. }
  1804. internal static void SetLevelInPropsIfSynced(object levelId)
  1805. {
  1806. if (!PhotonNetwork.AutomaticallySyncScene || !PhotonNetwork.IsMasterClient || PhotonNetwork.CurrentRoom == null)
  1807. {
  1808. return;
  1809. }
  1810. if (levelId == null)
  1811. {
  1812. Debug.LogError("Parameter levelId can't be null!");
  1813. return;
  1814. }
  1815. // check if "current level" is already set in the room properties (then we don't set it again)
  1816. if (PhotonNetwork.CurrentRoom.CustomProperties.ContainsKey(CurrentSceneProperty))
  1817. {
  1818. object levelIdInProps = PhotonNetwork.CurrentRoom.CustomProperties[CurrentSceneProperty];
  1819. //Debug.Log("levelId (to set): "+ levelId + " levelIdInProps: " + levelIdInProps + " SceneManagerHelper.ActiveSceneName: "+ SceneManagerHelper.ActiveSceneName);
  1820. if (levelId.Equals(levelIdInProps))
  1821. {
  1822. //Debug.LogWarning("The levelId equals levelIdInProps. Don't set property again.");
  1823. return;
  1824. }
  1825. else
  1826. {
  1827. // if the new levelId does not equal the level in properties, there is a chance that build index and scene name refer to the same scene.
  1828. // as Unity does not provide all scenes with build index, we only check for the currently loaded scene (with a high chance this is the correct one).
  1829. int scnIndex = SceneManagerHelper.ActiveSceneBuildIndex;
  1830. string scnName = SceneManagerHelper.ActiveSceneName;
  1831. if ((levelId.Equals(scnIndex) && levelIdInProps.Equals(scnName)) || (levelId.Equals(scnName) && levelIdInProps.Equals(scnIndex)))
  1832. {
  1833. //Debug.LogWarning("The levelId and levelIdInProps refer to the same scene. Don't set property for it.");
  1834. return;
  1835. }
  1836. }
  1837. }
  1838. // if the new levelId does not match the current room-property, we can cancel existing loading (as we start a new one)
  1839. if (_AsyncLevelLoadingOperation != null)
  1840. {
  1841. if (!_AsyncLevelLoadingOperation.isDone)
  1842. {
  1843. Debug.LogWarning("PUN cancels an ongoing async level load, as another scene should be loaded. Next scene to load: " + levelId);
  1844. }
  1845. _AsyncLevelLoadingOperation.allowSceneActivation = false;
  1846. _AsyncLevelLoadingOperation = null;
  1847. }
  1848. // current level is not yet in props, or different, so this client has to set it
  1849. Hashtable setScene = new Hashtable();
  1850. if (levelId is int) setScene[CurrentSceneProperty] = (int)levelId;
  1851. else if (levelId is string) setScene[CurrentSceneProperty] = (string)levelId;
  1852. else Debug.LogError("Parameter levelId must be int or string!");
  1853. PhotonNetwork.CurrentRoom.SetCustomProperties(setScene);
  1854. SendAllOutgoingCommands(); // send immediately! because: in most cases the client will begin to load and pause sending anything for a while
  1855. }
  1856. private static void OnEvent(EventData photonEvent)
  1857. {
  1858. int actorNr = photonEvent.Sender;
  1859. Player originatingPlayer = null;
  1860. if (actorNr > 0 && NetworkingClient.CurrentRoom != null)
  1861. {
  1862. originatingPlayer = NetworkingClient.CurrentRoom.GetPlayer(actorNr);
  1863. }
  1864. switch (photonEvent.Code)
  1865. {
  1866. case EventCode.Join:
  1867. ResetPhotonViewsOnSerialize();
  1868. break;
  1869. case PunEvent.RPC:
  1870. ExecuteRpc(photonEvent.CustomData as Hashtable, originatingPlayer);
  1871. break;
  1872. case PunEvent.SendSerialize:
  1873. case PunEvent.SendSerializeReliable:
  1874. // Debug.Log(photonEvent.ToStringFull());
  1875. /* This case must match definition in RunViewUpdate() and OnSerializeWrite().
  1876. * Format of the event's data object[]:
  1877. * [0] = PhotonNetwork.ServerTimestamp;
  1878. * [1] = currentLevelPrefix; OPTIONAL!
  1879. * [2] = object[] of PhotonView x
  1880. * [3] = object[] of PhotonView y or NULL
  1881. * [...]
  1882. *
  1883. * We only combine updates for XY objects into one RaiseEvent to avoid fragmentation.
  1884. * The Reliability and Interest Group are only used for RaiseEvent and not contained in the event/data that reaches the other clients.
  1885. * This is read in OnEvent().
  1886. */
  1887. object[] pvUpdates = (object[])photonEvent[ParameterCode.Data];
  1888. int remoteUpdateServerTimestamp = (int)pvUpdates[0];
  1889. short remoteLevelPrefix = (pvUpdates[1] != null) ? (byte)pvUpdates[1] : (short)0;
  1890. object[] viewUpdate = null;
  1891. for (int i = 2; i < pvUpdates.Length; i++)
  1892. {
  1893. viewUpdate = pvUpdates[i] as object[];
  1894. if (viewUpdate == null)
  1895. {
  1896. break;
  1897. }
  1898. OnSerializeRead(viewUpdate, originatingPlayer, remoteUpdateServerTimestamp, remoteLevelPrefix);
  1899. }
  1900. break;
  1901. case PunEvent.Instantiation:
  1902. NetworkInstantiate((Hashtable)photonEvent.CustomData, originatingPlayer);
  1903. break;
  1904. case PunEvent.CloseConnection:
  1905. // MasterClient "requests" a disconnection from us
  1906. if (PhotonNetwork.EnableCloseConnection == false)
  1907. {
  1908. Debug.LogWarning("CloseConnection received from " + originatingPlayer + ". PhotonNetwork.EnableCloseConnection is false. Ignoring the request (this client stays in the room).");
  1909. }
  1910. else if (originatingPlayer == null || !originatingPlayer.IsMasterClient)
  1911. {
  1912. Debug.LogWarning("CloseConnection received from " + originatingPlayer + ". That player is not the Master Client. " + PhotonNetwork.MasterClient + " is.");
  1913. }
  1914. else if (PhotonNetwork.EnableCloseConnection)
  1915. {
  1916. PhotonNetwork.LeaveRoom(false);
  1917. }
  1918. break;
  1919. case PunEvent.DestroyPlayer:
  1920. Hashtable evData = photonEvent.CustomData as Hashtable;
  1921. if (evData == null)
  1922. {
  1923. break;
  1924. }
  1925. int targetPlayerId = (int)evData[keyByteZero];
  1926. if (targetPlayerId >= 0)
  1927. {
  1928. DestroyPlayerObjects(targetPlayerId, true);
  1929. }
  1930. else
  1931. {
  1932. DestroyAll(true);
  1933. }
  1934. break;
  1935. case EventCode.Leave:
  1936. // destroy objects & buffered messages
  1937. if (CurrentRoom != null && CurrentRoom.AutoCleanUp && (originatingPlayer == null || !originatingPlayer.IsInactive))
  1938. {
  1939. DestroyPlayerObjects(actorNr, true);
  1940. }
  1941. break;
  1942. case PunEvent.Destroy:
  1943. evData = (Hashtable)photonEvent.CustomData;
  1944. int instantiationId = (int)evData[keyByteZero];
  1945. // Debug.Log("Ev Destroy for viewId: " + instantiationId + " sent by owner: " + (instantiationId / PhotonNetwork.MAX_VIEW_IDS == actorNr) + " this client is owner: " + (instantiationId / PhotonNetwork.MAX_VIEW_IDS == this.LocalPlayer.ID));
  1946. PhotonView pvToDestroy = null;
  1947. if (photonViewList.TryGetValue(instantiationId, out pvToDestroy))
  1948. {
  1949. RemoveInstantiatedGO(pvToDestroy.gameObject, true);
  1950. }
  1951. else
  1952. {
  1953. Debug.LogError("Ev Destroy Failed. Could not find PhotonView with instantiationId " + instantiationId + ". Sent by actorNr: " + actorNr);
  1954. }
  1955. break;
  1956. case PunEvent.OwnershipRequest:
  1957. {
  1958. int[] requestValues = (int[])photonEvent.CustomData;
  1959. int requestedViewId = requestValues[0];
  1960. int requestedFromOwnerId = requestValues[1];
  1961. PhotonView requestedView = GetPhotonView(requestedViewId);
  1962. if (requestedView == null)
  1963. {
  1964. Debug.LogWarning("Can't find PhotonView of incoming OwnershipRequest. ViewId not found: " + requestedViewId);
  1965. break;
  1966. }
  1967. if (PhotonNetwork.LogLevel == PunLogLevel.Informational)
  1968. {
  1969. Debug.Log(string.Format("OwnershipRequest. actorNr {0} requests view {1} from {2}. current pv owner: {3} is {4}. isMine: {6} master client: {5}", actorNr, requestedViewId, requestedFromOwnerId, requestedView.OwnerActorNr, requestedView.IsOwnerActive ? "active" : "inactive", MasterClient.ActorNumber, requestedView.IsMine));
  1970. }
  1971. switch (requestedView.OwnershipTransfer)
  1972. {
  1973. case OwnershipOption.Takeover:
  1974. int currentPvOwnerId = requestedView.OwnerActorNr;
  1975. if (requestedFromOwnerId == currentPvOwnerId || (requestedFromOwnerId == 0 && currentPvOwnerId == MasterClient.ActorNumber) || currentPvOwnerId == 0)
  1976. {
  1977. // a takeover is successful automatically, if taken from current owner
  1978. Player prevOwner = requestedView.Owner;
  1979. requestedView.OwnerActorNr = actorNr;
  1980. requestedView.ControllerActorNr = actorNr;
  1981. if (PhotonNetwork.OnOwnershipTransferedEv != null)
  1982. {
  1983. PhotonNetwork.OnOwnershipTransferedEv(requestedView, prevOwner);
  1984. }
  1985. }
  1986. else
  1987. {
  1988. if (PhotonNetwork.OnOwnershipTransferFailedEv != null)
  1989. {
  1990. PhotonNetwork.OnOwnershipTransferFailedEv(requestedView, originatingPlayer);
  1991. }
  1992. //Debug.LogWarning("requestedView.OwnershipTransfer was ignored! ");
  1993. }
  1994. break;
  1995. case OwnershipOption.Request:
  1996. if (PhotonNetwork.OnOwnershipRequestEv != null)
  1997. {
  1998. PhotonNetwork.OnOwnershipRequestEv(requestedView, originatingPlayer);
  1999. }
  2000. break;
  2001. default:
  2002. Debug.LogWarning("Ownership mode == " + (requestedView.OwnershipTransfer) + ". Ignoring request.");
  2003. break;
  2004. }
  2005. }
  2006. break;
  2007. case PunEvent.OwnershipTransfer:
  2008. {
  2009. int[] transferViewToUserID = (int[])photonEvent.CustomData;
  2010. int requestedViewId = transferViewToUserID[0];
  2011. int newOwnerId = transferViewToUserID[1];
  2012. if (PhotonNetwork.LogLevel >= PunLogLevel.Informational)
  2013. {
  2014. Debug.Log("Ev OwnershipTransfer. ViewID " + requestedViewId + " to: " + newOwnerId + " Time: " + Environment.TickCount % 1000);
  2015. }
  2016. PhotonView requestedView = GetPhotonView(requestedViewId);
  2017. if (requestedView != null)
  2018. {
  2019. // Only apply this if pv allows Takeover, or allows Request and this message originates from the controller or owner.
  2020. if (requestedView.OwnershipTransfer == OwnershipOption.Takeover ||
  2021. (requestedView.OwnershipTransfer == OwnershipOption.Request && (originatingPlayer == requestedView.Controller || originatingPlayer == requestedView.Owner)))
  2022. {
  2023. Player prevOwner = requestedView.Owner;
  2024. requestedView.OwnerActorNr= newOwnerId;
  2025. requestedView.ControllerActorNr = newOwnerId;
  2026. if (PhotonNetwork.OnOwnershipTransferedEv != null)
  2027. {
  2028. PhotonNetwork.OnOwnershipTransferedEv(requestedView, prevOwner);
  2029. }
  2030. }
  2031. else if (PhotonNetwork.LogLevel >= PunLogLevel.Informational)
  2032. {
  2033. if (requestedView.OwnershipTransfer == OwnershipOption.Request)
  2034. Debug.Log("Failed incoming OwnershipTransfer attempt for '" + requestedView.name + "; " + requestedViewId +
  2035. " - photonView has OwnershipTransfer set to OwnershipOption.Request, but Player attempting to change owner is not the current owner/controller.");
  2036. else
  2037. Debug.Log("Failed incoming OwnershipTransfer attempt for '" + requestedView.name + "; " + requestedViewId +
  2038. " - photonView has OwnershipTransfer set to OwnershipOption.Fixed.");
  2039. }
  2040. }
  2041. else if (PhotonNetwork.LogLevel >= PunLogLevel.ErrorsOnly)
  2042. {
  2043. Debug.LogErrorFormat("Failed to find a PhotonView with ID={0} for incoming OwnershipTransfer event (newOwnerActorNumber={1}), sender={2}",
  2044. requestedViewId, newOwnerId, actorNr);
  2045. }
  2046. break;
  2047. }
  2048. case PunEvent.OwnershipUpdate:
  2049. {
  2050. reusablePVHashset.Clear();
  2051. // Deserialize the list of exceptions, these are views on the master who's Owner and Creator didn't match.
  2052. int[] viewOwnerPair = (int[])photonEvent.CustomData;
  2053. for (int i = 0, cnt = viewOwnerPair.Length; i < cnt; i++)
  2054. {
  2055. int viewId = viewOwnerPair[i];
  2056. i++;
  2057. int newOwnerId = viewOwnerPair[i];
  2058. PhotonView view = GetPhotonView(viewId);
  2059. if (view == null)
  2060. {
  2061. if (PhotonNetwork.LogLevel >= PunLogLevel.ErrorsOnly)
  2062. {
  2063. Debug.LogErrorFormat("Failed to find a PhotonView with ID={0} for incoming OwnershipUpdate event (newOwnerActorNumber={1}), sender={2}. If you load scenes, make sure to pause the message queue.", viewId, newOwnerId, actorNr);
  2064. }
  2065. continue;
  2066. }
  2067. Player prevOwner = view.Owner;
  2068. Player newOwner = CurrentRoom.GetPlayer(newOwnerId, true);
  2069. view.OwnerActorNr= newOwnerId;
  2070. view.ControllerActorNr = newOwnerId;
  2071. reusablePVHashset.Add(view);
  2072. // If this produces an owner change locally, fire the OnOwnershipTransfered callbacks
  2073. if (PhotonNetwork.OnOwnershipTransferedEv != null && newOwner != prevOwner)
  2074. {
  2075. PhotonNetwork.OnOwnershipTransferedEv(view, prevOwner);
  2076. }
  2077. }
  2078. // Initialize all views. Typically this is just fired on a new client after it joins a room and gets the first OwnershipUpdate from the Master.
  2079. // This was moved from PhotonHandler OnJoinedRoom to here, to allow objects to retain controller = -1 until an controller is actually known.
  2080. foreach (var view in PhotonViewCollection)
  2081. {
  2082. if (!reusablePVHashset.Contains(view))
  2083. view.RebuildControllerCache();
  2084. }
  2085. break;
  2086. }
  2087. }
  2088. }
  2089. private static void OnOperation(OperationResponse opResponse)
  2090. {
  2091. switch (opResponse.OperationCode)
  2092. {
  2093. case OperationCode.GetRegions:
  2094. if (opResponse.ReturnCode != 0)
  2095. {
  2096. if (PhotonNetwork.LogLevel >= PunLogLevel.Full)
  2097. {
  2098. Debug.Log("OpGetRegions failed. Will not ping any. ReturnCode: " + opResponse.ReturnCode);
  2099. }
  2100. return;
  2101. }
  2102. if (ConnectMethod == ConnectMethod.ConnectToBest)
  2103. {
  2104. string previousBestRegionSummary = PhotonNetwork.BestRegionSummaryInPreferences;
  2105. if (PhotonNetwork.LogLevel >= PunLogLevel.Informational)
  2106. {
  2107. Debug.Log("PUN got region list. Going to ping minimum regions, based on this previous result summary: " + previousBestRegionSummary);
  2108. }
  2109. NetworkingClient.RegionHandler.PingMinimumOfRegions(OnRegionsPinged, previousBestRegionSummary);
  2110. }
  2111. break;
  2112. case OperationCode.JoinGame:
  2113. if (Server == ServerConnection.GameServer)
  2114. {
  2115. PhotonNetwork.LoadLevelIfSynced();
  2116. }
  2117. break;
  2118. }
  2119. }
  2120. private static void OnClientStateChanged(ClientState previousState, ClientState state)
  2121. {
  2122. if (
  2123. (previousState == ClientState.Joined && state == ClientState.Disconnected) ||
  2124. (Server == ServerConnection.GameServer && (state == ClientState.Disconnecting || state == ClientState.DisconnectingFromGameServer))
  2125. )
  2126. {
  2127. LeftRoomCleanup();
  2128. }
  2129. if (state == ClientState.ConnectedToMasterServer && _cachedRegionHandler != null)
  2130. {
  2131. BestRegionSummaryInPreferences = _cachedRegionHandler.SummaryToCache;
  2132. _cachedRegionHandler = null;
  2133. }
  2134. }
  2135. // to be used in the main thread. as OnRegionsPinged is called in a separate thread and so we can't use some of the Unity methods (like saving playerPrefs)
  2136. private static RegionHandler _cachedRegionHandler;
  2137. private static void OnRegionsPinged(RegionHandler regionHandler)
  2138. {
  2139. if (PhotonNetwork.LogLevel >= PunLogLevel.Informational)
  2140. {
  2141. Debug.Log(regionHandler.GetResults());
  2142. }
  2143. _cachedRegionHandler = regionHandler;
  2144. //PhotonNetwork.BestRegionSummaryInPreferences = regionHandler.SummaryToCache; // can not be called here, as it's not in the main thread
  2145. // the dev region overrides the best region selection in "development" builds (unless it was set but is empty).
  2146. #if UNITY_EDITOR
  2147. if (!PhotonServerSettings.DevRegionSetOnce)
  2148. {
  2149. // if no dev region was defined before or if the dev region is unavailable, set a new dev region
  2150. PhotonServerSettings.DevRegionSetOnce = true;
  2151. PhotonServerSettings.DevRegion = _cachedRegionHandler.BestRegion.Code;
  2152. }
  2153. #endif
  2154. #if DEVELOPMENT_BUILD || UNITY_EDITOR
  2155. if (!string.IsNullOrEmpty(PhotonServerSettings.DevRegion) && ConnectMethod == ConnectMethod.ConnectToBest)
  2156. {
  2157. Debug.LogWarning("PUN is in development mode (development build). As the 'dev region' is not empty (" + PhotonServerSettings.DevRegion + ") it overrides the found best region. See PhotonServerSettings.");
  2158. string _finalDevRegion = PhotonServerSettings.DevRegion;
  2159. if (!_cachedRegionHandler.EnabledRegions.Any(p => p.Code == PhotonServerSettings.DevRegion))
  2160. {
  2161. _finalDevRegion = _cachedRegionHandler.EnabledRegions[0].Code;
  2162. Debug.LogWarning("The 'dev region' (" + PhotonServerSettings.DevRegion + ") was not found in the enabled regions, the first enabled region is picked (" + _finalDevRegion + ")");
  2163. }
  2164. bool connects = PhotonNetwork.NetworkingClient.ConnectToRegionMaster(_finalDevRegion);
  2165. if (!connects)
  2166. {
  2167. PhotonNetwork.NetworkingClient.Disconnect(DisconnectCause.Exception);
  2168. }
  2169. return;
  2170. }
  2171. #endif
  2172. if (NetworkClientState == ClientState.ConnectedToNameServer)
  2173. {
  2174. bool connects = PhotonNetwork.NetworkingClient.ConnectToRegionMaster(regionHandler.BestRegion.Code);
  2175. if (!connects)
  2176. {
  2177. PhotonNetwork.NetworkingClient.Disconnect(DisconnectCause.Exception);
  2178. }
  2179. }
  2180. }
  2181. }
  2182. }