しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Unity で MessagePack for C# を利用する(ファイル入出力)

※ これは 2022/02/03 時点の Unity 2021.2.10f1、MessagePack for C# v2.3.85 の情報です

最新版では動作が異なる可能性がありますのでご注意ください

前回、MessagePack for C#シリアライズ、デシリアライズができたので、ファイルへの読み書きをやってみる

前回の SaveData.cs を拡張するためまずは IPersistentData クラスとその拡張クラスを下記のように追加

シリアライズ・デシリアライズ処理部分を SaveAsync/LoadAsync メソッドにしてファイル入出力処理をするようにした感じ

using Cysharp.Threading.Tasks;
using MessagePack;
using System;
using System.IO;
using System.Security;

namespace MatatabiUx.Common
{
    /// <summary>
    /// 永続化データのインタフェース
    /// </summary>
    public interface IPersistentData {}

    /// <summary>
    /// 永続化データを処理する拡張クラス
    /// </summary>
    public static class IPersistentDataExtentions
    {
        /// <summary>
        /// ファイルからデータを読み込む
        /// </summary>
        /// <param name="data">読み込むデータモデル</param>
        /// <param name="path">読み込み先パス</param>
        /// <typeparam name="T">読み込むデータモデルの型</typeparam>
        /// <returns>読み込んだデータ、ファイルがなかった場合はデフォルト値</returns>
        public static async UniTask<T> LoadAsync<T>(this T data, string path) where T : IPersistentData, new()
        {
            if (!File.Exists(path))
            {
                return new T();
            }
            try
            {
                using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read))
                    return await MessagePackSerializer.DeserializeAsync<T>(stream);
            }
            catch (IOException) { }
            catch (UnauthorizedAccessException) { }
            catch (SecurityException) { }

            return default;
        }

        /// <summary>
        /// ファイルにデータを書き込む
        /// </summary>
        /// <param name="data">書き込むデータモデル</param>
        /// <param name="path">書き込み先パス</param>
        /// <typeparam name="T">書き込むデータモデルの型</typeparam>
        /// <returns>書き込みが成功した場合は true, それ以外は false</returns>
        public static async UniTask<bool> SaveAsync<T>(this T data, string path) where T : IPersistentData, new()

        {
            try
            {
                using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write))
                    await MessagePackSerializer.SerializeAsync(stream, data);
                return true;
            }
            catch (IOException) { }
            catch (UnauthorizedAccessException) { }
            catch (SecurityException) { }

            return false;
        }
    }
}

さらに SaveData.common.cs として前回のデータ項目を記載したソースファイルとは別ファイルにして下記を記述

共通部分を partial で分離したのでデータ項目が増えてもこのソースファイルは変更しなくて済む

using Cysharp.Threading.Tasks;
using System;
using System.IO;
using UnityEngine;

namespace MatatabiUx.Common
{
    public partial class SaveData : IPersistentData
    {
        #region Singleton

        protected static SaveData Instance => instance
            ?? (instance = new SaveData(Path.Combine(Application.persistentDataPath, "save.dat")));
        private static SaveData instance = null;

        #endregion //Singleton

        #region File IO

        protected static string FilePath = null;

        protected SaveData(string filePath) : base()
        {
            FilePath = filePath;
        }

        /// <summary>
        /// データを読み込む
        /// </summary>
        /// <typeparam name="SaveData">セーブデータ</typeparam>
        /// <returns>読み込んだデータ</returns>
        public static UniTask<SaveData> LoadAsync() => Instance.LoadAsync<SaveData>(FilePath);

        /// <summary>
        /// データを書き込む
        /// </summary>
        /// <typeparam name="SaveData">セーブデータ</typeparam>
        /// <returns>書き込みが成功した場合は true, それ以外は false</returns>
        public UniTask<bool> SaveAsync()
        {
            this.Timestamp = DateTimeOffset.Now;
            return this.SaveAsync<SaveData>(FilePath);
        }

        #endregion //File IO
   }
}

この `SaveData のファイル読み書きを試すために下記のテストクラスを用意

using MatatabiUx.Common;
using UnityEngine;

public class Test : MonoBehaviour
{
    public void Awake()
    {
        // シリアライザの初期設定
        MessagePack.Resolvers.StaticCompositeResolver.Instance.Register(
            MessagePack.Resolvers.GeneratedResolver.Instance, // コード生成した型解決クラス
            MessagePack.Unity.UnityResolver.Instance,
            MessagePack.Unity.Extension.UnityBlitWithPrimitiveArrayResolver.Instance,
            MessagePack.Resolvers.StandardResolver.Instance
        );
        var option = MessagePack.MessagePackSerializerOptions.Standard
            .WithCompression(MessagePack.MessagePackCompression.Lz4BlockArray) // LZ4 圧縮利用
            .WithResolver(MessagePack.Resolvers.StaticCompositeResolver.Instance);
        MessagePack.MessagePackSerializer.DefaultOptions = option;
    }

    public async void Start()
    {
        // ファイルから読み込み
        var data = await SaveData.LoadAsync();

        UnityEngine.Debug.Log(
            $"Id={data.Id}, Timestamp={data.Timestamp}");

        // ファイルに書き込み
        await data.SaveAsync();
    }
}

とりあえず2回動かしてみてログを確認

読み書き確認

最初はデフォルト値で2回目はちゃんと保存日時が書きこまれているので大丈夫そう