Playerprefs memory crash

I’ve traced a large number of EXC_BAD_ACCESS crash reports to player prefs (by process of elimination). They are frequent when I’m using the player prefs, and thus far non-existent when I’m not.

This doesn’t happen in the editor.

I’m wondering if anyone else has had an issue with saving data to the iPhone.

In my case, I’m saving directly prior to loading another scene.

The exact call order looks something like this…

SaveData()
LoadOtherScene()
LoadDataWeJustSaved()

Are the playerprefs methods non-blocking? This would seem like an obvious oversight, but I don’t have another explanation for this behavior. I can try injecting yield statements, but that feels pretty sloppy.

Hmm, I do essentially the same thing with no probs. Maybe a data type issue?

How many data do you store?

No issues with PlayerPrefs here either. The operations are done immediately…saving values and then reading them back gets the new values; no reason to use yield.

–Eric

@dreamora I’m saving a grand total of 2 ints per level, with 15 levels. So 30 ints.

@Eric Are you sure they aren’t stored statically and saved to disk at some other point? I’ve crashed the game after PlayerPrefs saved (and successfully returned the saved data prior to crashing) - and then upon reboot realized the data reverted to an earlier saved state. This to me seems to indicate that PlayerPrefs caches the saved stuff, and then tries to save it. My guess is this, coupled with loading / unloading of levels is throwing it off.

Still I can say after 4 hours of testing today - with player prefs, the game crashes every 3-10 minutes. Without, I haven’t had a single crash in 4 hours across 3 devices.

I left all of the fundamental code intact, and simply commented out all the calls to the player prefs library

For all interested, these are a few of the exceptions…

Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x00000000, 0x00000000
Crashed Thread: 0

Exception Type: EXC_BAD_ACCESS (SIGABRT)
Exception Codes: KERN_PROTECTION_FAILURE at 0x00000000
Crashed Thread: 0

Exception Type: EXC_BAD_ACCESS (SIGABRT)
Exception Codes: 0x00000101, 0xc000005f
Crashed Thread: 0

Hello!

It looks like a bug to me, are you able to make a small project that reassembles the issue and fill in a bug report?

also description of your hardware specs, and description of what you where doing/trying to achieve will help the developers fix this problem.

I’ve submitted a bug report as of today. In the meantime, I need to find a workaround so we can ship. Has anyone explored accessing the iPhone filesystem using C# I/O?

I’ve gotten about as far as I can rewriting the playerprefs functionality. I’m a bumbling idiot on the iphone side with obj-c. I managed to get the filepath of the documents folder back, but it appears using the mono file writing protocols simply aren’t going to work, and I just don’t have the time to explore reading and writing data on the iphone using their libraries.

Here is the relevant code in case someone else is stopped dead by playerprefs as well, and knows how to save data using the iPhone libraries.

using UnityEngine;
using System.Collections;
using System.IO;
using System.Runtime.InteropServices;

using Jayrock.Json;
using Jayrock.Json.Conversion;

public static class FileAccess{
	
	private static string filePath = "";
	
	const string fileName = "GameData.txt";
	
	private static JsonObject tempData = new JsonObject();
	
	[DllImport ("__Internal")]
	private static extern string _GetPath ();
	
	
	static FileAccess(){
		LoadDocumentPath();
		tempData = new JsonObject();
		LoadDataFromDisk();	
	}
	
	
	
	private static void LoadDocumentPath(){
		if (Application.platform != RuntimePlatform.OSXEditor){
			filePath = _GetPath() + "/" + fileName;
		}
		else{
			filePath = fileName;
		}
	}
	
	public static bool HasKey(string key){
		return(tempData.Contains(key));		
	}
	public static void DeleteKey(string key){
		tempData.Put(key,null);	
		WriteDataToDisk();
	}
	//Ints
	public static void SetInt(string key, int val){
		tempData.Put(key,val);
		WriteDataToDisk();
	}
	public static int GetInt(string key, int defVal){
		if(HasKey(key)){
			
			try{ //Prevent Bad Data Types - could be more friendly in parsing...
				int returnVal = (int)(tempData[key]);
				return(returnVal);
			}
			catch(System.Exception e){
				Debug.Log(e);
				return (defVal);	
			}
		}
		else{
			return (defVal);	
		}
	}
	public static object GetInt(string key){
		return(GetInt(key,0));
	}
	
	//Floats
	public static void SetFloat(string key, float val){
		tempData.Put(key,val);
		WriteDataToDisk();
	}
	public static float GetFloat(string key, float defVal){
		if(HasKey(key)){
			
			try{ //Prevent Bad Data Types - could be more friendly in parsing...
				float returnVal = (float)(tempData[key]);
				return(returnVal);
			}
			catch(System.Exception e){
				Debug.Log(e);
				return (defVal);	
			}
		}
		else{
			return (defVal);	
		}
		
	}
	public static float GetFloat(string key){
		return(GetFloat(key,0.0f));
	}
	
	//Strings
	public static void SetString(string key, string val){
		tempData.Put(key,val);
		WriteDataToDisk();
	}
	public static string GetString(string key, string defVal){
		if(HasKey(key)){
			return (tempData[key] + "");
		}
		else{
			return defVal;	
		}
	}
	public static string GetString(string key){
		return GetString(key, "");
	}
	
	private static void LoadDataFromDisk(){
		FileStream dataFile = new FileStream(filePath, FileMode.OpenOrCreate);
		dataFile.Close();
		string file = "";
		using (StreamReader sr = new StreamReader(filePath)) {
	        string line;
	        // Read and display lines from the file until the end of 
	        // the file is reached.
	        while ((line = sr.ReadLine()) != null) {
	           
	            file += line; //only reads last line?
	        }
	         Debug.Log(file);
         }
         
         try{
        	 tempData = (JsonObject)JsonConvert.Import(file);
         }
         catch(System.Exception e){
         	Debug.Log(e); //no data
         }
	}
	
	private static void WriteDataToDisk(){
		
		FileStream dataFile = new FileStream(filePath, FileMode.Create);
		StreamWriter writer = new StreamWriter(dataFile);
		
//		Debug.Log("output " + JsonConvert.ExportToString(tempData));
		writer.WriteLine(JsonConvert.ExportToString(tempData));  //This won't clear the existing line
    	writer.Close();
	}
	

}

Here you go, I’ve used this in 4 shipped games and haven’t heard of any problems so far (this is from back before PlayerPrefs was working on iPhone):

using System.Collections;
using System.IO;

public class FileIO
{
	public static bool WriteStringToFile(string filePath, string data, bool append)
	{
		// Write a file (btw. add import System.IO; to use StreamReader/Writer)
		try
		{
			StreamWriter sw = new StreamWriter(filePath, append);

			sw.Write(data);
			sw.Close();
			return true;
		}
		catch (System.Exception err)
		{
			return false;
		}
	}

	// Reads each line of a text file to a separate string which is stored
	// in an ArrayList and returned.
	public static ArrayList ReadFileToStrings(string filePath)
	{
		ArrayList list = new ArrayList();
		string line;

		// Read a file
		try
		{
			if (!File.Exists(filePath))
				return list;

			StreamReader sr = new StreamReader(filePath);

			line = sr.ReadLine();
			if (line != null)
				list.Add(string.Copy(line));

			while (line != null)
			{
				line = sr.ReadLine();
				if (line != null)
					list.Add(string.Copy(line));
			}

			sr.Close();

			return list;
		}
		catch (System.Exception err)
		{
			return list;
		}
	}
}

And just use this to acquire the path where it is safe to write your game data:

string appDataPath = Application.dataPath.Substring(0, Application.dataPath.Length - 4) + "Documents";

Edit: That’s all in-unity, BTW. No need to do anything in Obj-C.

Hey!

I also had that issue with an app, actually… Once on an app that heavily utilises the playerprefs (I store about 100k or so of data) and one that doesn’t use it - at all.

Scenes got renamed, build settings changed, etc, and the problem appeared to just go away.

I could not build and run, I’d have to build, cancel the debug and then run manually.

I don’t use the debugging ever, so, minor issue for me :slight_smile:

I had the same problem as far as having to just run it without the debugger, though it couldn’t have been related to PlayerPrefs for me because I wasn’t using it, and it would stall before the app even finished loading to be able to run any loading code anyway. And, like you, it just mysteriously stopped happening one day.

Unity is awesome :slight_smile:

Wow. Thanks a bunch. I’ll get to work on this immediately and report back here.

I’m about 99% sure mine is player prefs related. Sadly, it looks like some sort of threading issue in the guts, because it can only be represented as a probability of a crash, at least from my code.

BTW, since this writes everything as strings, you can output int’s, bool’s and float’s with “var.ToString()”, and then read it back in from string like so:

var = float.Parse(myString);

Since the file reader returns an ArrayList of strings (each being a separate line in the text file), you’d do:

ArrayList lines = FileIO.ReadFileToStrings(path);
var1 = float.Parse((string)lines[0]);
var2 = float.Parse((string)lines[1]);

And so on…

Thanks so much. Your method for the data path seems to be enough. I have a few data type issues to handle, but I think its under control.

Having expounded in this thread http://forum.unity3d.com/viewtopic.php?t=32082 about try - catch blocks being stripped by build stripping beyond ‘Strip Assemblies,’ I am theorizing that those build stripping settings are also the cause of the problems in the Player Prefs.

I think it’s safe to presume Player Prefs use try catch blocks, since it is fundamentally doing file I/O, and that would be a safe way to do it. Also, there are two examples above designed to recreate player prefs, both of which use try catch blocks, naturally.

However, upon building the code to the iPhone, I noticed the try catch blocks were being ignored.
It appears the biggest difference between my project and the others who have posted is the use of the more aggressive build stripping.
They had problems with neither player prefs, nor try - catch blocks, and I had problems with both.

Therefore, I believe it is a safe hypothesis that the more aggressive stripping is breaking both try - catch, as well as player prefs. (And potentially any other internal unity functions using try catch).

I recommend only using Strip Assemblies, and I’ll report this to Unity.

Hi:

Brady, how you would go about to resume on a specific level with your simple saving script on Application Quit. Can you provide an example.

Thank you,

Chepe

You would basically just build a string containing the data you want to save, in this case, a level number and a score:

string savedata = levelNum.ToString(); // for a float or int, you can omit “ToString”
savedata += “\n” + score; // put the score on the second line

Then just pass this string to the FileIO script and it will be written.

To read it back in, see my previous example. Basically though, you know that the first line (line 0) returned in the ArrayList by the read function will be the level, and the second (line 1) will be the score.

Hi Brady:

I will try it with a basic sample of two levels, just to see if it works. I’m not a great programmer, but I will give it a shot.

Stay tune and Thank you again!

Chepe :smile:

Brady:

First of all, I want to thank you for posting that code.

I was struggling with Unity 3, iPad and SQLite for a project I had to deliver pretty soon.

After two days of fighting with constant crashes on the iPad, I’ve decided to go back to Unity iPhone and redo the whole project. Problem is, on Unity iPhone I don’t have System.Data or SQLiteData from Mono.

So I used your code and adapted it to my needs and it worked exceptionally great.

So, thanks a lot for sharing.

On a side note, and just in case somebody has this same issue, I had to make one small change in order for this to work on the iPad, and it’s regarding the path to the Documents folder.

I had to use::

appDataPath = Application.dataPath +  "/../../Documents/";

Instead of the Substring form.