- Home /
Strange nullReferenceError when dealing with Hashtables and Photon
Hey guys,
This one is a real mind bender, and i've been struggling with this for the past few days.
To keep this short, I'm building the foundations of a multiplayer game using Photon within Unity. Photon allows you to apply custom properties to certain server objects (such as a player or a "room") by passing a hashtable to the SetCustomProperties() method, which seems like a very useful tool. However this problem is disallowing me the ability to use this.
So, in order to make things more rigid, i'm trying to use objects instead of hashtables, and i've created a simple class to easily convert the object to a hashtable, and convert the hashtable to the object. Here is the basic class that for setting and getting the custom room properties:
using UnityEngine;
using System.Collections;
public class PhotonRoomInformation {
public string type = ""; //this will either be "quick" or "custom"
public string status = ""; //this will be either "game" or "lobby"
public int maxPlayers = 0;
public int numPlayers = 0;
public PhotonRoomInformation()
{
this.type = "";
this.status = "";
this.maxPlayers = 0;
this.numPlayers = 0;
}
//get hashatable from the manually set parameters
public Hashtable getHashTable()
{
Hashtable ht = new Hashtable();
ht.Add("type",type);
ht.Add("status",status);
ht.Add("maxPlayers",maxPlayers);
ht.Add("numPlayers",numPlayers);
return ht;
}
//set the properties from a passed hashtable
public void setProperties(Hashtable ht)
{
this.type = (string)ht["type"];
this.status = (string)ht["status"];
this.maxPlayers = (int)ht["maxPlayers"];
this.numPlayers = (int)ht["numPlayers"];
}
}
Now, using the getHashTable() method works fine. I have tested this thoroughly by seeing what the server recieves and sends back. Everything matches up.
However, when I try to use the setProperties() method, by passing the customProperties hashtable of the room to the method, I get a null reference error.
Here is the part of code where I am trying to perform this method:
//when the rooms properties have updated, check that the room is full. If so, we're ready to load the game
void OnPhotonCustomRoomPropertiesChanged()
{
PhotonRoomInformation pri = new PhotonRoomInformation();
pri.setProperties(PhotonNetwork.room.customProperties);
//if the status is in game, load the level
if(pri.status == "game")
{
print ("loading level!");
PhotonNetwork.LoadLevel(quickMatchScene);
}
}
Which gives me this exact error: NullReferenceException: Object reference not set to an instance of an object PhotonRoomInformation.setProperties (System.Collections.Hashtable ht) (at Assets/Standard Assets/Scripts/Utilities/PhotonRoomInformation.cs:36) quickmatchMenu.OnPhotonCustomRoomPropertiesChanged () (at Assets/Standard Assets/Scripts/Menus/quickmatchMenu.cs:186)
Line 36 of PhotonRoomInformation.cs (the class shown in this post) is this:
this.maxPlayers = (int)ht["maxPlayers"];
Line 186 of quickmatchMenu.cs is part of the extract shown above:
pri.setProperties(PhotonNetwork.room.customProperties);
Any help would be greatly appreciated.
It seems the Hashtable passed to setProperties method doesn't contain "maxPlayers" key. In this case ht["maxPlayers"] returns null and converting it to an int causes exception.
To check what keys are available in your Hashtable, please paste this script at the begin of your setProperties method:
foreach(var key in ht.$$anonymous$$eys)
{
Debug.Log(key + ": " + ht[key]);
}
and check Unity log for output.
Hey buddy, yeah this is something I did initially, as I thought the same thing.
That being said, just to be sure I've done as you asked and none of the values were returned as null:
type: quick
numPlayers: 1
maxPlayers: 2
status: lobby
That's the copied and pasted printed values.
Could you possibly delete this answer and post it as a comment to my question?
It is marking this post as answered when it clearly is not answered. $$anonymous$$any thanks.
Of course - converted (there is an option to convert question to comment and comment to question, so no need to delete).
And returning to your issue. If you can debug this script, then this would be the best option, because you can quickly discover what's going on. If you can't debug, then maybe ins$$anonymous$$d of:
this.maxPlayers = (int)ht["maxPlayers"];
you can temporarily use
var tmp1 = ht["maxPlayers"];
Debug.Log(tmp1);
Debug.Log(tmp1.GetType());
var tmp2 = (int)tmp1;
Debug.Log(tmp2);
this.maxPlayers = tmp2;
I'm curious what the output will be...
Answer by donnysobonny · Aug 09, 2013 at 03:00 AM
This was a rather peculiar one, however with the help of @ArkaneX I was able to locate the problem.
Ultimately, Photon comes with an Enum method: OnPhotonCustomRoomPropertiesChanged() which is called when the properties of the room that you are in have changed. I was initially using this method to check the status of the room.
After using the debug script that @ArkaneX posted above, I realised that it looked as if the OnPhotonCustomRoomPropertiesChanged method was being called before the property changes were actually available to read, then again once the property changes were available. Because of the null value when the method was being called initially, it was causing problems with my script.
To fix this, I am now checking if any of the values of the passed HashTable are null, if so, I try again a short period after.
I will let Photon know of this, as I cannot imagine that this is intentional.
Thanks again for the help.
Answer by tobiass · Aug 09, 2013 at 09:53 AM
First of all, I didn't run your code just yet but you do more work than necessary: Use the built-in (or "well-known") properties RoomInfo.playerCount and .maxPlayers.
If you want to sync the loaded level, you can use PhotonNetwork.LoadLevel() if you set PhotonNetwork.automaticallySyncScene to true for your app. This uses properties as well but you don't have to re-do the work.
For me, it looks like properties should be available in OnPhotonCustomRoomPropertiesChanged(), as I cache them first. It depends a bit on your workflow though.
Example: You test with 2 clients. The first creates the room and when it's in the room, it sets the properties. Depending on your timing, the second client might join the room before after it's created but before the props are set. I will have to test if the first call to OnPhotonCustomRoomPropertiesChanged is always done when the room is entered (no matter if there are actual custom props set).
In all cases, if the props are not set on create (and thus potentially not yet set when you join), you need to check for null. But you don't have to check them periodically, as we always call either OnPhotonCustomRoomPropertiesChanged or OnPhotonPlayerPropertiesChanged respectively.
Hey @tobias,
As always, I much appreciated your help.
Yeah, since originally posting this i've made quite a few changes to the program$$anonymous$$g of it all. I honestly don't know why I didn't use the playerCount and maxPlayers properties of the Room class... I guess I had a momentary moment of madness!
Anyways, co$$anonymous$$g back to this problem, I'll be honest and say that since I did the work-around by checking if the hashtable is null and rerunning the function until I get a populated hashtable, i've not bothered to go back and change it. However yeah, it does always make the call to OnPhotonCustomRoomPropertiesChanged twice. So I guess for some reason the joining of a room is triggering the change of it's properties (which would make sense...), and would pretty much guarantee that the properties wont be available on the first call if they have just been set and I am just picking them up within the first OnPhotonCustomRoomPropertiesChanged, particularly considering we're using hashtables here. =p
I would hate to leave the code this way, i'm not really a fan of work-arounds. If you do manage to fix this in a later patch so that it only makes a call to the OnPhotonCustomRoomPropertiesChanged when custom properties have actually been changed, or passed within the CreateRoom() method, please do stick a reply in here to let me and others know.