I love strippers! aka Network Errors Build Stripping question

I am implementing the UDP + RPC type of networking as described in:
Unity Multiplayer Tutorial
For using Networking in Unity.
Author: Andrius Kuznecovas Last Revision: 24-MAR-2010

I’ll put my (work in progress) source code that is causing this problem, in a followup post

Using Unity 3.2 Pro. Platform = iOS and Android

When stripping is set to use micro mscorlib, the editor fails to build:
UnityException: Failed assemblies stripper (long exception…snip)
Unhandled Exception: Mono.Linker.ResolutionException: Can not resolve reference: System.Void

When stripping is set to to Strip Bytecode,it build I get a JIT Error at runtime iOS device
ExecutionEngineException: Attempting to JIT compile method ‘GameManagerState__TypeMetadata:.ctor ()’ while running with --aot-only.

When stripping is set to Strip Assemblies, it builds, but I get the same JIT error as above.

When stripping is set to Disabled, it builds, but I get the same JIT error as above.

This seems like a bug to me, but I have not attempted networking with Unity on iOS before.

Should I replace Threads with Coroutines?

Please advise, iOS + Unity experts. Thanks,

Alex

using UnityEngine;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Threading;

public class UnityMPWifiNetMgr : BaseNetMgr
{
	public DominosNetPlayer netPlayerPrefab;
	
	private NetworkView cachedNetView;
		
	private UdpClient udpServer;
	private UdpClient udpClient;
	private IPEndPoint udpReceiveEndPoint;
	private string udpServerPort = "40400";
	private int unityServerPort = 40401;
	private string ip = "0.0.0.0";
	private string ipBroadcast = "255.255.255.255";
	private bool connected = false;
	private string serverName = "";
	private Thread clientThread;
	private Thread serverThread;
	
	public bool IsHost {
		get {
			return (GameManager.instance.State.ClientType == ClientType.Host );
		}
	}
		
	public NetworkView CachedNetView {
		get {
			if (cachedNetView == null)
				cachedNetView = GetComponent<NetworkView> ();
			return this.cachedNetView;
		}
		set {
			cachedNetView = value;
		}
	}
	
	public void Awake ()
	{
		Application.runInBackground = true;
	}
	
	public override void Start ()
	{
		base.Start ();
		StartCoroutine(BootMPGame());
	}
		
	public void OnDestroy ()
	{
		
	}
	
	public override void OnDoPlayerConnect (object sender, BasePlayer player)
	{
		// notify local delegates		
		base.OnDoPlayerConnect (sender, player);
		
		// if we are Host, then RPC call OnDoPlayerConnect's equivalent method
		if (IsHost)
		{
			CachedNetView.RPC ("OnDoPlayerConnectRPC", RPCMode.OthersBuffered, player.Nic, player.NetPlayer);
		}
	}
	
	[RPC]	
	public void OnDoPlayerConnectRPC (string nic, NetworkPlayer netPlayer, NetworkMessageInfo netInfo)
	{
		Debug.Log ("RPC call rec'd: " + netInfo);
		
		// create NetPlayer, assign nic and NetPlayer reference, then add to Players
		DominosNetPlayer newPlayer = Instantiate (
			netPlayerPrefab,
			GameManager.instance.otherPlayerSpawnPoint.position,
			Quaternion.identity
			) as DominosNetPlayer;
		newPlayer.Nic = nic;
		newPlayer.NetPlayer = netPlayer;
		GameManager.instance.Players.Add (newPlayer);
		
		// notify local delegates
		base.OnDoPlayerConnect (this, newPlayer);
	}

	public override void OnDoPlayerDisconnect (object sender, BasePlayer player)
	{
		// notify local delegates
		base.OnDoPlayerDisconnect (sender, player);

		// if we are Host, then RPC call OnDoPlayerConnect's equivalent method
		if (IsHost) 
		{
			CachedNetView.RPC ("OnDoPlayerConnectRPC", RPCMode.OthersBuffered, player.Nic, player.NetPlayer);
		}
	}
	
	// these delegate messages may have come from local senders, or via RPC call
	public override void OnDoGameState (object sender, GameState newState)
	{
		// notify local delegates. 
		base.OnDoGameState (sender, newState);
			
		// notify remote delegates via RPC, if we are host
		if (GameManager.instance.State.ClientType != ClientType.Host)
			return;	
	}

	public override void OnDoPlayerTurnState (object sender, BasePlayer player, PlayerTurnStates newState)
	{
		// notify local delegates. 
		base.OnDoPlayerTurnState (sender, player, newState);
		
		// notify remote delegates via RPC, if we are host
		if (GameManager.instance.State.ClientType != ClientType.Host)
			return;
	}

	public override void OnPlayBone (object sender, BasePlayer player, Bone playBone, Bone parentBone, BonePosition pos)
	{
		// notify local delegates. 
		base.OnPlayBone (sender, player, playBone, parentBone, pos);
		
	
		if (GameManager.instance.State.ClientType != ClientType.Host)
			return;
		
		// notify remote delegates via RPC, if we are host
	}
	
		
	private IEnumerator BootMPGame ()
	{
		GameManager.instance.State.ClientType = ClientType.Unknown;

        #if UNITY_IPHONE || UNITY_ANDROID
		if (iPhoneSettings.internetReachability == iPhoneNetworkReachability.NotReachable) 
		{
			Debug.LogWarning ("Internet not reachable");
			yield return new WaitForSeconds (1);
		}
		#endif
		
		// the udp client will start a thread, listen on port, for broadcast of server name 
		// then the ServerName property will be set
		LoadUDPClient ();
		yield return new WaitForSeconds (1);
		
		if (serverName != string.Empty) 
		{
			Debug.LogWarning ("client located server @ " + serverName);
			Debug.LogWarning("client connecting....");
			Network.Connect (serverName, unityServerPort);
		} 
		else 
		{
			Debug.LogWarning ("no udp server detected");
			
			// force udpClient and thread to exit first
			connected = true;
			connected = false;
			udpClient.Close ();
			yield return 0;
			
			Debug.LogWarning("udp client stopped");
			
			// become the server
			LoadUDPServer ();
		}
		
		if(GameManager.instance.State.ClientType == ClientType.Host)
		{
			//add DevicePlayer as Player 0
			GameManager.instance.Players.Clear();
			DevicePlayer.instance.NetPlayer = Network.player;
			GameManager.instance.Players.Add( DevicePlayer.instance );			
				
			while( GameManager.instance.Players.Count <  GameManager.instance.State.MaxPlayers )
			{
				Debug.LogWarning("waiting for players");
				yield return new WaitForSeconds(1);
			}
		}
	}

	void OnFailedToConnect (NetworkConnectionError error)
	{
		Debug.LogWarning ("Could not connect to server: " + error);
	}
	
	void OnFailedToConnectToMasterServer (NetworkConnectionError info)
	{
		Debug.LogWarning ("Could not connect to master server: " + info);
	}
	
	void OnDisconnectedFromServer (NetworkDisconnection info)
	{
		if (Network.isServer)			
			Debug.LogWarning ("Local server connection disconnected"); 
		else if (info == NetworkDisconnection.LostConnection)
			Debug.LogWarning ("Lost connection to the server");
		else
			Debug.LogWarning ("Successfully diconnected from the server");
	}
	
	public void OnServerInitialized ()
	{
		Debug.LogWarning ("Unity Network Server initialized");
		
		connected = true;
		GameManager.instance.State.ClientType = ClientType.Host;
		
		try
		{
			ip = Network.player.ipAddress.ToString ();
			udpServer = new UdpClient (System.Convert.ToInt32 (udpServerPort));
			udpReceiveEndPoint = new IPEndPoint (IPAddress.Parse (ip), System.Convert.ToInt32 (udpServerPort));
			serverThread = new Thread (new ThreadStart (UDPServerTask));
			serverThread.Start ();
		}
		catch {}
		
		Debug.LogWarning ("UDP broadcasting of server ip: " + ip);
	}
	
	public void OnConnectedToServer ()
	{
		Debug.LogWarning ("Connected to server");
		
		connected = true;
		GameManager.instance.State.ClientType = ClientType.RemoteClient;
	}
	
	void OnPlayerConnected (NetworkPlayer player)
	{				
		Debug.LogWarning ("Player connected from " + player.ipAddress + ":" + player.port);
	}

	void OnPlayerDisconnected (NetworkPlayer player)
	{
		Debug.LogWarning ("Server destroying player");
		Network.RemoveRPCs (player, 0);
		Network.DestroyPlayerObjects (player);
	}
	
	/// <summary>
	/// creates receive point and client, starts the udpclient thread
	/// </summary>
	private void LoadUDPClient ()
	{
		Debug.LogWarning ("Starting UDP client...");
		
		try
		{
			udpClient = new UdpClient (System.Convert.ToInt32 (udpServerPort));
			udpReceiveEndPoint = new IPEndPoint (IPAddress.Parse (ip), System.Convert.ToInt32 (udpServerPort));
			clientThread = new Thread (new ThreadStart (UDPClientTask));
			clientThread.Start ();
			Debug.LogWarning ("UDP client started");
		}
		catch
		{}		
	}

	/// <summary>
	/// Reads the server name from any udp broadcasts.
	/// </summary>
	private void UDPClientTask ()
	{
		try {
			while (true) 
			{
				byte[] recData = udpClient.Receive (ref udpReceiveEndPoint);
				System.Text.ASCIIEncoding encode = new System.Text.ASCIIEncoding ();
				serverName = encode.GetString (recData);
				
				if (serverName != string.Empty)
				{
					// if the server was selected above, and unity network connected, then close
					// the udp client and break out of thread
					if (connected)
					{
						udpClient.Close ();
						break;
					}
				}
			}
		} catch {}
	}
	
	/// <summary>
	/// Inits the Unity Server, and starts a thread to broadcast over udp.
	/// </summary>
	private void LoadUDPServer ()
	{
		Debug.LogWarning("Becoming Server...");
		Network.InitializeSecurity ();
		Network.InitializeServer (GameManager.instance.State.MaxPlayers, unityServerPort, false);
	}
	
	/// <summary>
	/// broadcasts our ip address over udp. it will be read as ServerName property by the client.
	/// </summary>
	private void UDPServerTask ()
	{
		try {
			while (true) 
			{
				System.Text.ASCIIEncoding encode = new System.Text.ASCIIEncoding ();
				byte[] sendData = encode.GetBytes (ip);
				udpServer.Send (sendData, sendData.Length, ipBroadcast, System.Convert.ToInt32 (udpServerPort));
				Thread.Sleep (100);
			}
		} catch {
		}
	}			
}

After a lot trial and error compilation, and I discovered this is the offending line of code which will not compile when
build stripping set to: use micro mscorlib

// private UdpClient udpServer
udpServer.Send (sendData, sendData.Length, ipBroadcast, System.Convert.ToInt32 (udpServerPort));

Is this is a bug with the stripping? I don’t understand why it fails on that single line of code. as opposed to when the UdpClient object was created.

The other errors about JIT- well I guess those have to do with [Serializable] not working right when build stripping is NOT set to use micro mscorlib, which is another mystery unto itself.

UdpClient.Send() doesn’t compile with use micro mscorlib (Bug?)

[Serializable] and BinaryFormatter.Serialize throws runtime exceptions UNLESS use micro mscorlib

So I am stuck between a rock and a hard place. Need to either use XML serialization (and add additional 1.2MB of .dll) or rewrite my game saving to be less fancy I guess.

Aha! just in case anyone else goes down this same nefarious path :slight_smile:
I discovered that BinaryFormatter works much better on iOS, if you implement ISerializable. I guess if you don’t do that, it has to use dynamic reflection and/or generics to figure out the object at runtime.
Yay!

Micro mscorlib is not compatible with .NET sockets. It was designed to support very minimal set of .NET classes that required by UnityEngine.dll to run (like System.String and few others).

Usually I advice people to avoid BinaryFormatter on iOS and make custom serializer, which actually happens when you implement ISerializable.

I’m experiencing the very same problem now on iOS. I need to serialize use RPC, I confirm that Binary serialization won’t work outside mscorlib and RPC won’t work inside.

I’ve also tried with protobuf-net serialization but I’m encountering hard problems (unity 3.5.2f2)

After hours of work, I still don’t have a working serialization mechanism for my game :frowning:

EDIT: finally I’ve managed how to work with protobuf-net, at writing time those information are still ok, but you have to follow them strictly: you HAVE TO build 2 dll, one for the Model and another for the ModelSerializer, the second one is generated through ProtoBuf TypeModel Compile

arkanoid, glad you got the protobuf-net working (the same solution I settled upon!) and yes you must precompile dlls for the model and the serializer. Another ios-compatible serialization alternative is a stripped down JSON parser, such as the one distributed by prime31. Cheers!