見出し画像

ダンスゲーを作りたい#33 【内部処理】データ受け取り呼び出し記述の単一化改修と新規登録処理記述の備忘録

以前、というかほんとここ最近まとめ直したばっかりな気がしますが、基盤作成で使っていたプロジェクトから別プロジェクトへ移している最中、データの読み込みがなんかできなくなるという大問題に直面しました。

正直理由が全くわかりません。

というかもはやどんな処理しててどこで失敗してるのかすらわかりません。

そこで、どんな理由でそういう処理にしたとか形にしたとかを備忘録としてできるだけ詳しく書き残します。

将来の自分よ。忘れたらこれを見るのだ。

これは完全に僕のプログラム内で起こった問題に僕が対処法を忘れないための備忘録なので、普通に読んでも参考程度になるかならないかが関の山ですので悪しからず。

呼び出し方法

扱いたいデータは大きく処理の異なる3種類に分けてあります。それに関しては過去記事参照。

サーバデータは読み込みを個別で行います。

認証を要するローカルデータは一括更新してる気がしますが、個別でいいことに気が付いたので個別更新に切り替えます。

認証不要のローカルデータも個別更新でいいです。

呼び出し時にサーバアクセスやデータチェックをさせるためにUpdate()とかなんとか書いてましたが、面倒なのでその辺を全部まとめて

hogeData data = await DataManager.GetData<hogeData>(new hogeData()); 

の形で画一化することにします。

データ更新の時困りそうという問題は、更新処理はすべてリクエストになることを前提に、DataManagerにUpdate(更新用情報);みたいな関数を設けて、これで画一化します。

ここでお気づきでしょうが、個別取得なのでデータインスタンスの保持は一切行いません。読み出しだけです。

データマネージャはすでに貯蔵庫ではなく窓口な点に注意してください。

呼び出し時の内部処理

サーバデータとローカルデータはそれぞれリクエストしてそのまま返すだけにします。

認証を要するデータは、認証して保存して読み込んで返します。

そのまま返すデータの処理

DataManager内部に呼び出し口を作ります

    public async Task<T> GetData<T>(T datainstance) where T : SOdataMaster{
       await datainstance.Update();
       return datainstance;
   }

型制約を親クラスで制限したいところですが、クラスで指定する場合一つしか指定できないようなので、それぞれのデータの抽象クラスにさらに抽象クラスを設けました。

public abstract class SOdataMaster{
   public abstract void Update();
}

generic型でinstanceを作って内部で関数実行させるのは面倒なので、呼び出し側で事前にデータインスタンスを作ることにしてます。

呼び出すデータはSOServer,Localdatasの派生クラスである必要があるため、内部には必ず自分のデータを更新するUpdate()関数を持っていると想定します。これは、値の更新処理リクエスト用のUpdateとは別物な点に注意してください。

Updateが走ると、各instanceではメンバの値を外部から最新のデータを読み込む処理が走ります。この処理中はロード画面を表示させるため非同期的に行われます。

この一連の処理をまとめたSOServer,Localdatasという抽象クラスの内部処理はこうなります。


public abstract class SOServerdatas:SOdataMaster{
   Manager Manager{
       get{return GameObject.Find("Manager").GetComponent<Manager>();}
   }
   protected abstract string URL{get;}
   public abstract Task<bool> Sync(JObject Data);
   
   public override async Task<bool> Update(){
       Manager.PhaseManager.Loading=true;
       try{
           AccountData ACD = await Manager.GameDataManager.GetData<AccountData>(new AccountData());
           var parameters = new Dictionary<string,string>();
           parameters.Add("ID",ACD.ID.ToString());
           parameters.Add("secID",ACD.secID.ToString());
           JObject res = await Task.Run(()=> ServerAccess(parameters));
           await Sync(res);
       }catch(Exception e){
           Manager.PhaseManager.Loading=false;
           return false;
       }
       Manager.PhaseManager.Loading=false;
       return true;
   }

   public async Task<JObject> ServerAccess(Dictionary<string, string> parameters){
       var content = new FormUrlEncodedContent(parameters);
       using (var client = new HttpClient()){
           var response = await client.PostAsync(URL, content);
           Thread.Sleep(1000);
           if (response.StatusCode!=HttpStatusCode.OK){
               return null;
           }else{
               JObject Obj = JObject.Parse(await response.Content.ReadAsStringAsync());
               return Obj;
           }
       }
   }
}
public abstract class SOLocaldatas:SOdataMaster{
   Manager Manager{
       get{return GameObject.Find("Manager").GetComponent<Manager>();}
   }
   public abstract Task<bool> Sync(JObject Data);

   //外から呼ぶやつ。更新しろって言いたいとき使う
   public override async Task<bool> Update(){
       //ロード画面を呼び出す
       Manager.PhaseManager.Loading=true;
       try{
           JObject res =  await Task.Run(()=> FileAccess());
           await Sync(res); //個別でシンクする
       }catch(Exception e){
           Manager.PhaseManager.Loading=false;
           return false;
       }
       Manager.PhaseManager.Loading=false;
       return true;
   }

   async Task<JObject> FileAccess(){
       //フォルダからファイルを読み出し、対象データを格納
       JObject Obj = null;
       //失敗しても5回まではやってよし
       for(int i=0;i<5;i++){
           bool flg = false;
           try{
               if(File.Exists(ConfigDatas.LocalDataPath)){//既にデータが保存済み
                   //データを読み込んでOBJに格納
                   var json = File.ReadAllText(ConfigDatas.LocalDataPath);
                   Obj = JObject.Parse(json);
                   flg=true;
               }
           }catch{
               flg=false;//何かしら、エラーが出たら、もう一回。
           }
           if(flg){
               break;
           }
           if(i==4&&!flg){
               return null;
           }
       }
       return Obj;
   }
}

認証を要するローカルデータ

上記二つをくっつけたみたいな感じです。

なんか読み込めてなかった原因は作ってる最中に気づきましたが、Sync()が非同期に対応していなかったせいでSync完了前に処理が終了してしまっていたのがおそらく主な原因です。

public abstract class SOUserdatas:SOdataMaster{
   Manager Manager{
       get{return GameObject.Find("Manager").GetComponent<Manager>();}
   }
   string URL = ConfigDatas.LocalDataCheckURL;
   public abstract Task<bool> Sync(JObject Data);

   public override async Task<bool> Update(){
       //ロード画面を呼び出す
       Manager.PhaseManager.Loading=true;
       bool isOKData=false;
       //データの変更チェックだけする
       try{
           isOKData = await Task.Run(()=> ServerAccess());
           Debug.Log(isOKData);
           if(isOKData){
               //変更されてないなら全ローカルデータの更新処理を実行
               await SyncLocal();
           }else{
               //変更されている場合ホームに戻す
               Manager.PhaseManager.Loading=false;
               HackOut();
               return false;
           }
       }catch(Exception e){
           Manager.PhaseManager.Loading=false;
           return false;
       }
       Manager.PhaseManager.Loading=false;
       return true;
   }
   void HackOut(){
       //データ不正検出の旨のメッセージ
       //ホーム画面に戻す処理
   }
   string UserDataPath=ConfigDatas.UserDataPath;
   public async Task<bool> ServerAccess(){
       //ローカルに保存してるデータを取得してポスト
       var parameters = new Dictionary<string, string>(){
           { "LocalData",File.ReadAllText(UserDataPath)}
       };
       var content = new FormUrlEncodedContent(parameters);
       using (var client = new HttpClient()){
           var response = await client.PostAsync(URL, content);
           
           Thread.Sleep(1000);

           if (response.StatusCode!=HttpStatusCode.OK){
               //通信失敗したらデータチェック失敗
               return false;
           }else{
               //レスポンスのisOKをboolに入れて返す。
               JObject Obj = JObject.Parse(await response.Content.ReadAsStringAsync());
               bool isOK = Obj["isOK"].ToObject<bool>();
               return isOK;
           }
       }
   }
   
   public async Task<bool> SyncLocal(){
       //ファイルからローカルデータを読み出し
       try{
           JObject res = await Task.Run(()=> FileAccess()); //nullかオブジェクトが返ってくる
           if(res==null){
               //エラーメッセージデータ更新失敗
               return false;
           }
           return await Sync(res); //個別でシンクする
       }catch(Exception e){
           Debug.Log(e);
           //エラーメッセージデータ更新失
           return false;
       }
   }
   
   async Task<JObject> FileAccess(){
       //フォルダからファイルを読み出し、対象データを格納
       JObject Obj = null;
       //失敗しても5回まではやってよし
       for(int i=0;i<5;i++){
           bool flg = false;
           try{
               if(File.Exists(ConfigDatas.UserDataPath)){//既にデータが保存済み
                   //データを読み込んでOBJに格納
                   var json = File.ReadAllText(ConfigDatas.UserDataPath);
                   Obj = JObject.Parse(json);
                   flg=true;
               }
           }catch{
               flg=false;//何かしら、エラーが出たら、もう一回。
           }
           if(flg){
               break;
           }
           if(i==4&&!flg){
               return null;
           }
       }
       return Obj;
   }
}

検証結果

究極サーバとローカルは別にどうでもいいといえばどうでもいいのでSOUserDatas型のものが読み込めるかだけ検証しておきました。

AccountData ACD = await Manager.GameDataManager.GetData<AccountData>(new AccountData());
Debug.Log(ACD.secID);

これで呼びだした際の正常系挙動ログは次の通り。

画像2

データクラス追加時の手順

[DataContract]
public class AccountData : SOUserdatas {
   [DataMember(Name="ID")]
   public int ID ;
   [DataMember(Name="secID")]
   public int secID ;
   public override async Task<bool> Sync(JObject obj){
       try{
           string jsonStr = JsonConvert.SerializeObject(obj["AccountData"], Formatting.None);
           Debug.Log(jsonStr);
           AccountData Data = JsonUtility.FromJson<AccountData>(jsonStr);  
           ID=Data.ID;
           secID=Data.secID;
       }catch{
           return false;
       }
       return true;
   }
}

属性に合致する継承クラスを継承し、必要事項を記入。

サーバ側でデータを追加する処理も忘れずに。

データが変更はされていないが取得できない場合はDataManager.GetData()内のUpate()がfalseで帰ってくるので、ここに処理を追加すればよろし。

以上。

この記事が気に入ったらサポートをしてみませんか?