diff --git a/Editor.meta b/Editor.meta
new file mode 100644
index 0000000..c1f6aa8
--- /dev/null
+++ b/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 81aeaf8d2b465be4fb33785ca5e74bf0
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/GitEditorProcess.cs b/Editor/GitEditorProcess.cs
new file mode 100644
index 0000000..7e5a8e7
--- /dev/null
+++ b/Editor/GitEditorProcess.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Diagnostics;
+
+namespace GameLovers.Services.Editor
+{
+ ///
+ /// Run git commands processes that would otherwise be used in the terminal.
+ ///
+ ///
+ /// https://blog.somewhatabstract.com/2015/06/22/getting-information-about-your-git-repository-with-c/
+ ///
+ public class GitEditorProcess : IDisposable
+ {
+ private const string DefaultPathToGitBinary = "git";
+
+ private readonly Process Process;
+
+ ///
+ ///
+ ///
+ public int ExitCode => Process.ExitCode;
+
+ public GitEditorProcess(string workingDir, string pathToGitBinary = DefaultPathToGitBinary)
+ {
+ var startInfo = new ProcessStartInfo
+ {
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ FileName = pathToGitBinary,
+ CreateNoWindow = true,
+ WorkingDirectory = workingDir
+ };
+
+ Process = new Process { StartInfo = startInfo };
+ }
+
+ ///
+ /// Is this unity project a git repo?
+ ///
+ public bool IsValidRepo()
+ {
+ return ExecuteCommand("rev-parse --is-inside-work-tree") == "true";
+ }
+
+ ///
+ /// Get the git branch name of the unity project.
+ ///
+ public string GetBranch()
+ {
+ return ExecuteCommand("rev-parse --abbrev-ref HEAD");
+ }
+
+ ///
+ /// Get the git commit hash of the unity project.
+ ///
+ public string GetCommitHash()
+ {
+ return ExecuteCommand($"rev-parse HEAD");
+ }
+
+ ///
+ /// Get the diff of the working directory in its current state from the state it was at at
+ /// a given commit.
+ ///
+ public string GetDiffFromCommit(string commitHash)
+ {
+ return ExecuteCommand($"diff --word-diff=porcelain {commitHash} -- {Process.StartInfo.WorkingDirectory}");
+ }
+
+ ///
+ public void Dispose()
+ {
+ Process?.Dispose();
+ }
+
+ ///
+ /// Execute a command eg. "status --verbose"
+ ///
+ private string ExecuteCommand(string args)
+ {
+ Process.StartInfo.Arguments = args;
+ Process.Start();
+ var output = Process.StandardOutput.ReadToEnd().Trim();
+ Process.WaitForExit();
+ return output;
+ }
+ }
+}
diff --git a/Editor/GitEditorProcess.cs.meta b/Editor/GitEditorProcess.cs.meta
new file mode 100644
index 0000000..c42436e
--- /dev/null
+++ b/Editor/GitEditorProcess.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1a90f1c0b9bc22e428b6325a25a87f40
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/VersionEditorUtils.cs b/Editor/VersionEditorUtils.cs
new file mode 100644
index 0000000..2af2afe
--- /dev/null
+++ b/Editor/VersionEditorUtils.cs
@@ -0,0 +1,141 @@
+using System;
+using System.IO;
+using UnityEditor;
+using UnityEngine;
+
+namespace GameLovers.Services.Editor
+{
+ ///
+ /// Set the internal version in any VersionService instances in the project before building
+ /// and whenever the project loads.
+ ///
+ public static class VersionEditorUtils
+ {
+ private const int ShortenedCommitLength = 8;
+
+ ///
+ /// Set the internal version before building the app.
+ ///
+ public static void SetAndSaveInternalVersion(bool isStoreBuild)
+ {
+ var newVersionData = GenerateInternalVersionSuffix(isStoreBuild);
+ var newVersionDataSerialized = JsonUtility.ToJson(newVersionData);
+ var oldVersionDataSerialized = LoadVersionDataSerializedSync();
+ if (newVersionDataSerialized.Equals(oldVersionDataSerialized, StringComparison.Ordinal))
+ {
+ return;
+ }
+
+ Debug.Log($"Saving new version data: {newVersionDataSerialized}");
+ SaveVersionData(newVersionDataSerialized);
+ }
+
+ ///
+ /// Loads the game version saved in disk into string format
+ ///
+ public static string LoadVersionDataSerializedSync()
+ {
+ var textAsset = Resources.Load(VersionServices.VersionDataFilename);
+ if (!textAsset)
+ {
+ Debug.LogError("Could not load internal version from Resources.");
+ return string.Empty;
+ }
+
+ var serialized = textAsset.text;
+ Resources.UnloadAsset(textAsset);
+ return serialized;
+ }
+
+ ///
+ /// Set the internal version for when the app plays in editor.
+ ///
+ [InitializeOnLoadMethod]
+ private static void OnEditorLoad()
+ {
+ SetAndSaveInternalVersion(false);
+ }
+
+ private static VersionServices.VersionData GenerateInternalVersionSuffix(bool isStoreBuild)
+ {
+ var data = new VersionServices.VersionData();
+
+ using (var repo = new GitEditorProcess(Application.dataPath))
+ {
+ try
+ {
+ if (!repo.IsValidRepo())
+ {
+ Debug.LogWarning("Project is not a git repo. Internal version not set.");
+ }
+ else
+ {
+ var branch = repo.GetBranch();
+ if (string.IsNullOrEmpty(branch))
+ {
+ Debug.LogWarning("Could not get git branch for internal version");
+ }
+ else
+ {
+ data.BranchName = branch;
+ }
+
+ var commitHash = repo.GetCommitHash();
+ if (string.IsNullOrEmpty(commitHash))
+ {
+ Debug.LogWarning("Could not get git commit for internal version");
+ }
+ else
+ {
+ data.Commit = commitHash.Substring(0, ShortenedCommitLength);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.LogException(e);
+ Debug.LogWarning("Could not execute git commands. Internal version not set.");
+ }
+ }
+
+ data.BuildNumber = PlayerSettings.iOS.buildNumber;
+ data.BuildType = isStoreBuild ? "prod" : "dev";
+
+ return data;
+ }
+
+ ///
+ /// Set the internal version of this application and save it in resources. This should be
+ /// called at edit/build time.
+ ///
+ private static void SaveVersionData(string serializedData)
+ {
+ const string assets = "Assets";
+ const string resources = "Build/Resources";
+
+ var absDirPath = Path.Combine(Application.dataPath, resources);
+ if (!Directory.Exists(absDirPath))
+ {
+ Directory.CreateDirectory(absDirPath);
+ }
+
+ // delete old file with incorrect extension
+ const string assetExtension = ".asset";
+ var absFilePath = Path.Combine(absDirPath, VersionServices.VersionDataFilename);
+ if (File.Exists(Path.ChangeExtension(absFilePath, assetExtension)))
+ {
+ AssetDatabase.DeleteAsset(
+ Path.Combine(assets, resources,
+ Path.ChangeExtension(VersionServices.VersionDataFilename, assetExtension)));
+ }
+
+ // create new text file
+ const string textExtension = ".txt";
+ File.WriteAllText(Path.ChangeExtension(absFilePath, textExtension), serializedData);
+
+ AssetDatabase.ImportAsset(
+ Path.Combine(assets, resources,
+ Path.ChangeExtension(VersionServices.VersionDataFilename, textExtension)));
+ }
+ }
+}
diff --git a/Editor/VersionEditorUtils.cs.meta b/Editor/VersionEditorUtils.cs.meta
new file mode 100644
index 0000000..c244fad
--- /dev/null
+++ b/Editor/VersionEditorUtils.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 34cc2f450515a0f4a8aad7814fbfef39
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/Installer.cs b/Runtime/Installer.cs
index 4f9a4cb..be207a4 100644
--- a/Runtime/Installer.cs
+++ b/Runtime/Installer.cs
@@ -1,3 +1,4 @@
+using GameLovers.Services;
using System;
using System.Collections.Generic;
@@ -21,7 +22,43 @@ public interface IInstaller
///
/// Thrown if the given doesn't implement interface
///
- void Bind(T instance) where T : class;
+ ///
+ /// This installer reference to allow chain calls if necessayr
+ ///
+ IInstaller Bind(T instance) where T : class;
+
+ ///
+ /// Binds the interface multiple type interefaces to the given
+ ///
+ ///
+ /// Thrown if the given doesn't implement all type interface
+ ///
+ ///
+ /// This installer reference to allow chain calls if necessayr
+ ///
+ IInstaller Bind(T1 instance) where T1 : class, T2;
+
+ ///
+ /// Binds the interface multiple type interefaces to the given
+ ///
+ ///
+ /// Thrown if the given doesn't implement all type interface
+ ///
+ ///
+ /// This installer reference to allow chain calls if necessayr
+ ///
+ IInstaller Bind(T1 instance) where T1 : class, T2, T3;
+
+ ///
+ /// Binds the interface multiple type interefaces to the given
+ ///
+ ///
+ /// Thrown if the given doesn't implement all type interface
+ ///
+ ///
+ /// This installer reference to allow chain calls if necessayr
+ ///
+ IInstaller Bind(T1 instance) where T1 : class, T2, T3, T4;
///
/// Tries requesting the instance binded to the type
@@ -51,14 +88,13 @@ public interface IInstaller
void Clean();
}
-
///
public class Installer : IInstaller
{
private readonly Dictionary _bindings = new Dictionary();
///
- public void Bind(T instance) where T : class
+ public IInstaller Bind(T instance) where T : class
{
var type = typeof(T);
@@ -68,6 +104,95 @@ public void Bind(T instance) where T : class
}
_bindings.Add(type, instance);
+
+ return this;
+ }
+
+ ///
+ public IInstaller Bind(T1 instance) where T1 : class, T2
+ {
+ var type1 = typeof(T1);
+ var type2 = typeof(T2);
+
+ if (!type1.IsInterface)
+ {
+ throw new ArgumentException($"Cannot bind {instance} because {type1} is not an interface");
+ }
+
+ if (!type2.IsInterface)
+ {
+ throw new ArgumentException($"Cannot bind {instance} because {type1} is not an interface");
+ }
+
+ _bindings.Add(type1, instance);
+ _bindings.Add(type2, instance);
+
+ return this;
+ }
+
+ ///
+ public IInstaller Bind(T1 instance) where T1 : class, T2, T3
+ {
+ var type1 = typeof(T1);
+ var type2 = typeof(T2);
+ var type3 = typeof(T3);
+
+ if (!type1.IsInterface)
+ {
+ throw new ArgumentException($"Cannot bind {instance} because {type1} is not an interface");
+ }
+
+ if (!type2.IsInterface)
+ {
+ throw new ArgumentException($"Cannot bind {instance} because {type2} is not an interface");
+ }
+
+ if (!type3.IsInterface)
+ {
+ throw new ArgumentException($"Cannot bind {instance} because {type3} is not an interface");
+ }
+
+ _bindings.Add(type1, instance);
+ _bindings.Add(type2, instance);
+ _bindings.Add(type3, instance);
+
+ return this;
+ }
+
+ ///
+ public IInstaller Bind(T1 instance) where T1 : class, T2, T3, T4
+ {
+ var type1 = typeof(T1);
+ var type2 = typeof(T2);
+ var type3 = typeof(T3);
+ var type4 = typeof(T4);
+
+ if (!type1.IsInterface)
+ {
+ throw new ArgumentException($"Cannot bind {instance} because {type1} is not an interface");
+ }
+
+ if (!type2.IsInterface)
+ {
+ throw new ArgumentException($"Cannot bind {instance} because {type2} is not an interface");
+ }
+
+ if (!type3.IsInterface)
+ {
+ throw new ArgumentException($"Cannot bind {instance} because {type3} is not an interface");
+ }
+
+ if (!type4.IsInterface)
+ {
+ throw new ArgumentException($"Cannot bind {instance} because {type4} is not an interface");
+ }
+
+ _bindings.Add(type1, instance);
+ _bindings.Add(type2, instance);
+ _bindings.Add(type3, instance);
+ _bindings.Add(type4, instance);
+
+ return this;
}
///
diff --git a/package.json b/package.json
index ef79fe5..b50007c 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "com.gamelovers.services",
"displayName": "Services",
"author": "Miguel Tomas",
- "version": "0.8.0",
+ "version": "0.8.1",
"unity": "2021.4",
"license": "MIT",
"description": "The purpose of this package is to provide a set of services to ease the development of a basic game architecture",