• Manual
  • API Documentation
  • Extend
  • Storage

    Show / Hide Table of Contents
    • Introduction
    • Installation
    • Getting Started
    • Demos
    • The Save System API
    • Auto Save
    • Platforms
    • Security
    • Serialization
    • Supported Data Types
    • Migration
    • FAQ
    • Changelog
    • Extend
      • Overview
      • Converter
      • Encryption
      • Storage
    • Guides
      • Overview
      • Configuring Settings
      • Using Auto Save
      • Basic Saving and Loading
      • Saving and Loading Raw Data
      • Deleting the Data
      • Saving and Loading Images
      • Saving and Loading Scene Objects
      • Saving and Loading Assets
      • Saving and Loading Readonly Properties
    • Integrations
      • Overview
      • Google Play Games
      • Xbox Live
      • Bolt
      • Firebase
      • PlayFab
      • PlayMaker
      • Steam Auto-Cloud
      • Steamworks .NET
      • Facepunch Steamworks
      • Text Mesh Pro
    • Storage
      • Overview
      • Backup
      • Catalog
      • Meta Data
      • Built-in Storages
        • File
        • PlayerPrefs
        • Memory
      • Third-party Storages
        • Firebase
          • Realtime Database
          • Cloud Storage
          • Firestore
        • PlayFab
          • Entity Objects
          • Entity Files
          • User Data
        • Steam
          • Steamworks .NET
          • Facepunch Steamworks
        • Google Play
          • Saved Game
        • Xbox Live
          • Connected Storage

    Storage

    A storage implementation provides methods for accessing and applying operations on the storage through a Unified API.

    There are a few built-in storage implementations such as:

    • File
    • PlayerPrefs

    Which are good inspirations for implementing your own storage on top of them.

    Also, the Storage API uses the async syntax so you can implement Online / Cloud storages too easily.

    So here we have the PlayerPrefs storage implementation:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using System.Threading.Tasks;
    using UnityEngine;
    
    namespace Bayat.SaveSystem.Storage
    {
    
        /// <summary>
        /// PlayerPrefs storage implementation.
        /// </summary>
        public class PlayerPrefsStorage : StorageBase
        {
    
            protected bool useBase64 = true;
    
            public PlayerPrefsStorage(string encodingName, bool useBase64) : base(encodingName)
            {
                this.useBase64 = useBase64;
            }
    
            public override Task<IStorageStream> GetWriteStream(string identifier)
            {
                PlayerPrefsStorageStream storageStream = new PlayerPrefsStorageStream(identifier, new MemoryStream());
                return Task.FromResult<IStorageStream>(storageStream);
            }
    
            protected override Task CommitWriteStreamInternal(IStorageStream stream)
            {
                PlayerPrefsStorageStream memoryStream = (PlayerPrefsStorageStream)stream;
                string data = null;
                if (useBase64)
                {
                    data = Convert.ToBase64String(((MemoryStream)memoryStream.UnderlyingStream).ToArray());
                }
                else
                {
                    this.TextEncoding.GetString(((MemoryStream)memoryStream.UnderlyingStream).ToArray());
                }
                PlayerPrefs.SetString(memoryStream.Identifier, data);
                return Task.CompletedTask;
            }
    
            public override Task<IStorageStream> GetReadStream(string identifier)
            {
                MemoryStream memoryStream = null;
                if (useBase64)
                {
                    memoryStream = new MemoryStream(Convert.FromBase64String(PlayerPrefs.GetString(identifier)));
                }
                else
                {
                    memoryStream = new MemoryStream(this.TextEncoding.GetBytes(PlayerPrefs.GetString(identifier)));
                }
                PlayerPrefsStorageStream storageStream = new PlayerPrefsStorageStream(identifier, memoryStream);
                return Task.FromResult<IStorageStream>(storageStream);
            }
    
            protected override Task WriteAllTextInternal(string identifier, string data)
            {
                PlayerPrefs.SetString(identifier, data);
                return Task.CompletedTask;
            }
    
            public override Task<string> ReadAllText(string identifier)
            {
                return Task.FromResult(PlayerPrefs.GetString(identifier));
            }
    
            protected override Task WriteAllBytesInternal(string identifier, byte[] data)
            {
                PlayerPrefs.SetString(identifier, Convert.ToBase64String(data));
                return Task.CompletedTask;
            }
    
            public override Task<byte[]> ReadAllBytes(string identifier)
            {
                return Task.FromResult(Convert.FromBase64String(PlayerPrefs.GetString(identifier)));
            }
    
            public override async Task<StorageClearOperationResult> Clear()
            {
                bool result = true;
                List<string> catalog = await LoadCatalog();
                foreach (string identifier in catalog)
                {
                    result &= (await Delete(identifier)).Succeed;
                }
                return new StorageClearOperationResult(result, catalog.ToArray());
            }
    
            protected override Task<StorageDeleteOperationResult> DeleteInternal(string identifier)
            {
                bool result = false;
                if (PlayerPrefs.HasKey(identifier))
                {
                    PlayerPrefs.DeleteKey(identifier);
                    result = true;
                }
                return Task.FromResult(new StorageDeleteOperationResult(result));
            }
    
            public override Task<bool> Exists(string identifier)
            {
                return Task.FromResult(PlayerPrefs.HasKey(identifier));
            }
    
            protected override async Task<StorageMoveOperationResult> MoveInternal(string oldIdentifier, string newIdentifier, bool replace)
            {
                bool result = false;
                if (PlayerPrefs.HasKey(oldIdentifier))
                {
                    if (replace || !PlayerPrefs.HasKey(newIdentifier))
                    {
                        PlayerPrefs.SetString(newIdentifier, PlayerPrefs.GetString(oldIdentifier));
                        await Delete(oldIdentifier);
                        result = true;
                    }
                }
                return new StorageMoveOperationResult(result, newIdentifier);
            }
    
            protected override Task<StorageCopyOperationResult> CopyInternal(string fromIdentifier, string toIdentifier, bool replace)
            {
                bool result = false;
                if (PlayerPrefs.HasKey(fromIdentifier))
                {
                    if (replace || !PlayerPrefs.HasKey(toIdentifier))
                    {
                        PlayerPrefs.SetString(toIdentifier, PlayerPrefs.GetString(fromIdentifier));
                        result = true;
                    }
                }
                return Task.FromResult(new StorageCopyOperationResult(result, toIdentifier));
            }
    
            public override async Task<string[]> List(string identifier, StorageListOptions options)
            {
                List<string> items = await LoadCatalog();
                if (options.MaxResults.HasValue)
                {
                    items.Capacity = options.MaxResults.GetValueOrDefault();
                }
                return items.FindAll(item => item.Contains(identifier)).ToArray();
            }
    
            public override async Task<string[]> ListAll()
            {
                return (await LoadCatalog()).ToArray();
            }
    
        }
    
        /// <summary>
        /// The PlayerPrefs storage stream wrapper.
        /// </summary>
        public class PlayerPrefsStorageStream : StorageStream
        {
    
            public PlayerPrefsStorageStream(string identifier, Stream stream) : base(identifier, stream)
            {
            }
    
        }
    
    }
    

    Now this requires a connection string factory in order to initialize it through a connection string, instead of instantiating the class directly, so lets do it by implementing the IConnectionFactory interface:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    namespace Bayat.SaveSystem.Storage
    {
    
        /// <summary>
        /// The built-in connection factory.
        /// </summary>
        public class BuiltInConnectionFactory : IConnectionFactory
        {
    
            public IStorage CreateStorage(StorageConnectionString connectionString)
            {
                if (connectionString.Prefix == "disk")
                {
                    //connectionString.GetRequired("path", true, out string path, "base-path", "folder", "directory", "dir");
                    string path = connectionString.Get("path", Application.persistentDataPath, "base-path", "folder", "directory", "dir");
    
                    return new LocalDiskStorage(path);
                }
    
                if (connectionString.Prefix == "playerprefs")
                {
                    string encodingName = connectionString.Get("encoding", StorageBase.DefaultTextEncodingName, "text-encoding", "encoding-name");
                    bool useBase64 = connectionString.GetBoolean("usebase64", true, "use-base64");
    
                    return new PlayerPrefsStorage(encodingName, useBase64);
                }
    
                return null;
            }
    
        }
    
    }
    

    The CreateStorage method is called automatically by the main connection string factory.

    Now when we use playerprefs://, it will initialize the PlayerPrefs storage.

    • Improve this Doc
    • 0 Comments