initial commit

This commit is contained in:
Jo 2025-01-07 02:06:59 +01:00
parent 6715289efe
commit 788c3389af
37645 changed files with 2526849 additions and 80 deletions

View file

@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
namespace UnityEditor.Build.CacheServer
{
/// <summary>
/// The CacheServerUploader is responsible for uploading assets to a given Cache Server.
/// </summary>
public static class CacheServerUploader
{
private struct Transaction
{
public struct FileInfo
{
public readonly FileType type;
public readonly string path;
public FileInfo(FileType type, string path)
{
this.type = type;
this.path = path;
}
}
public readonly FileId fileId;
public readonly FileInfo[] files;
private Transaction(FileId fileId, FileInfo[] files)
{
this.fileId = fileId;
this.files = files;
}
public static Transaction CreateForAssetPath(string assetPath)
{
var projectRoot = Directory.GetParent(Application.dataPath).FullName;
var guid = AssetDatabase.AssetPathToGUID(assetPath);
var hash = AssetDatabase.GetAssetDependencyHash(assetPath);
var libPath =
new[] { projectRoot, "Library", "metadata", guid.Substring(0, 2), guid }
.Aggregate(string.Empty, Path.Combine);
if (!File.Exists(libPath))
{
throw new Exception("Cannot find Library representation for GUID " + guid);
}
var files = new List<FileInfo>
{
new FileInfo(FileType.Asset, libPath)
};
var infoLibPath = libPath + ".info";
if (File.Exists(infoLibPath))
{
files.Add(new FileInfo(FileType.Info, infoLibPath));
}
var resLibPath = libPath + ".resource";
if (File.Exists(resLibPath))
{
files.Add(new FileInfo(FileType.Resource, resLibPath));
}
return new Transaction(FileId.From(guid, hash.ToString()), files.ToArray());
}
}
/// <summary>
/// Synchronize project library with the configured Cache Server.
/// </summary>
public static void UploadAllFilesToCacheServer()
{
string host;
int port;
Util.ParseCacheServerIpAddress(Util.ConfigCacheServerAddress, out host, out port);
UploadAllFilesToCacheServer(host, port);
}
/// <summary>
/// Synchronize project library folder with a remote Cache Server.
/// </summary>
/// <param name="host">Host name or IP or remote Cache Server</param>
/// <param name="port">Port number for remote Cache Server</param>
public static void UploadAllFilesToCacheServer(string host, int port)
{
var client = new Client(host, port);
client.Connect();
var assetPaths = AssetDatabase.GetAllAssetPaths();
var len = assetPaths.Length;
for (var i = 0; i < len; i++)
{
var path = assetPaths[i];
if (!File.Exists(path))
continue;
var progress = (float)(i + 1) / (len + 1);
if (EditorUtility.DisplayCancelableProgressBar("Uploading to Cache Server", path, progress)) break;
try
{
var trx = Transaction.CreateForAssetPath(path);
client.BeginTransaction(trx.fileId);
foreach (var file in trx.files)
using (var stream = new FileStream(file.path, FileMode.Open, FileAccess.Read))
client.Upload(file.type, stream);
client.EndTransaction();
}
catch (Exception e)
{
Debug.LogError(e);
break;
}
}
EditorUtility.ClearProgressBar();
client.Close();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e766efa9ccaf441728f810262e5ca359
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,90 @@
using System;
using UnityEngine;
namespace UnityEditor.Build.CacheServer
{
/// <summary>
/// The Cache Server Uploader window. This interface will upload your assets to a the given address of a Cache Server.
/// </summary>
public class CacheServerUploaderWindow : EditorWindow
{
private string m_address;
private void Awake()
{
m_address = Util.ConfigCacheServerAddress;
titleContent = new GUIContent("CS Upload");
}
private bool ValidateAddress()
{
string host;
int port;
Util.ParseCacheServerIpAddress(m_address, out host, out port);
var c = new Client(host, port);
try
{
c.Connect();
c.Close();
}
catch (Exception e)
{
Debug.LogError(e);
return false;
}
return true;
}
private void OnGUI()
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Cache Server Address: ");
m_address = GUILayout.TextField(m_address);
EditorGUILayout.EndHorizontal();
GUILayout.FlexibleSpace();
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Upload") && EditorUtility.DisplayDialog("Upload to Cache Server",
"This will upload all assets in your Library folder to the specified Cache Server.", "Continue", "Cancel"))
{
GetWindow<CacheServerUploaderWindow>().Close();
if (!ValidateAddress())
{
Debug.LogError("Could not connect to Cache Server");
return;
}
string host;
int port;
Util.ParseCacheServerIpAddress(m_address, out host, out port);
CacheServerUploader.UploadAllFilesToCacheServer(host, port);
}
if (GUILayout.Button("Cancel"))
{
GetWindow<CacheServerUploaderWindow>().Close();
}
EditorGUILayout.EndHorizontal();
}
private void OnInspectorUpdate()
{
Repaint();
}
/// <summary>
/// Uploads all assets to the cache server.
/// </summary>
[MenuItem("Assets/Cache Server/Upload All Assets")]
public static void UploadAllFilesToCacheServerMenuItem()
{
var window = GetWindow<CacheServerUploaderWindow>();
window.ShowUtility();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c18f0df0496b5418bbb5a34c4c30d6e8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,541 @@
using System;
using System.Collections;
using System.Text;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using UnityEngine;
using System.Threading;
namespace UnityEditor.Build.CacheServer
{
/// <summary>
/// Options for the type of a particular file.
/// </summary>
public enum FileType
{
/// <summary>
/// Use to indicate that the file is an asset.
/// </summary>
Asset = 'a',
/// <summary>
/// Use to indicate that the file holds information for an asset/resource.
/// </summary>
Info = 'i',
/// <summary>
/// Use to indicate that the file is a resource.
/// </summary>
Resource = 'r'
}
/// <summary>
/// Options for the result returned by a download operation.
/// </summary>
public enum DownloadResult
{
/// <summary>
/// Use to indicate that the operation failed.
/// </summary>
Failure = 0,
/// <summary>
/// Use to indicate that the operation failed because it could not locate the specified file.
/// </summary>
FileNotFound = 1,
/// <summary>
/// Use to indicate that the operation succedeed.
/// </summary>
Success = 2
}
/// <summary>
/// A GUID/Hash pair that uniquely identifies a particular file. For each FileId, the Cache Server can store a separate
/// binary stream for each FileType.
/// </summary>
public struct FileId : IEqualityComparer
{
/// <summary>
/// The guid byte array.
/// </summary>
public readonly byte[] guid;
/// <summary>
/// The hash code byte array.
/// </summary>
public readonly byte[] hash;
/// <summary>
/// A structure used to identify a file by guid and hash code.
/// </summary>
/// <param name="guid">File GUID.</param>
/// <param name="hash">File hash code.</param>
private FileId(byte[] guid, byte[] hash)
{
this.guid = guid;
this.hash = hash;
}
/// <summary>
/// Create a FileId given a string guid and string hash code representation.
/// </summary>
/// <param name="guidStr">GUID string representation.</param>
/// <param name="hashStr">Hash code string representation.</param>
/// <returns></returns>
public static FileId From(string guidStr, string hashStr)
{
if (guidStr.Length != 32)
throw new ArgumentException("Length != 32", "guidStr");
if (hashStr.Length != 32)
throw new ArgumentException("Length != 32", "hashStr");
return new FileId(Util.StringToGuid(guidStr), Util.StringToHash(hashStr));
}
/// <summary>
/// Create a FileId given a byte array guid and byte array hash code.
/// </summary>
/// <param name="guid">GUID byte array.</param>
/// <param name="hash">Hash code byte array.</param>
/// <returns></returns>
public static FileId From(byte[] guid, byte[] hash)
{
if (guid.Length != 16)
throw new ArgumentException("Length != 32", "guid");
if (hash.Length != 16)
throw new ArgumentException("Length != 32", "hash");
return new FileId(guid, hash);
}
/// <summary>
/// Check equality of two objects given their guid and hash code.
/// </summary>
/// <param name="x">lhs object.</param>
/// <param name="y">rhs object.</param>
/// <returns></returns>
public new bool Equals(object x, object y)
{
var hash1 = (byte[])x;
var hash2 = (byte[])y;
if (hash1.Length != hash2.Length)
return false;
for (var i = 0; i < hash1.Length; i++)
if (hash1[i] != hash2[i])
return false;
return true;
}
/// <summary>
/// Get the hash code for a specific object.
/// </summary>
/// <param name="obj">The object you want the hash code for.</param>
/// <returns></returns>
public int GetHashCode(object obj)
{
var hc = 17;
hc = hc * 23 + guid.GetHashCode();
hc = hc * 23 + hash.GetHashCode();
return hc;
}
}
/// <summary>
/// Exception thrown when an upload operation is not properly isolated within a begin/end transaction
/// </summary>
public class TransactionIsolationException : Exception
{
/// <summary>
/// Creates a new exception for when an upload operation is not properly isolated within a begin/end transaction.
/// </summary>
/// <param name="msg">The text containing information to display.</param>
public TransactionIsolationException(string msg) : base(msg) {}
}
/// <summary>
/// EventArgs passed to the DownloadFinished event handler
/// </summary>
public class DownloadFinishedEventArgs : EventArgs
{
/// <summary>
/// EventArgs download result code.
/// </summary>
public DownloadResult Result { get; set; }
/// <summary>
/// The downloaded item.
/// </summary>
public IDownloadItem DownloadItem { get; set; }
/// <summary>
/// The size of the downloaded item.
/// </summary>
public long Size { get; set; }
/// <summary>
/// The length of the download queue.
/// </summary>
public long DownloadQueueLength { get; set; }
}
/// <summary>
/// A client API for uploading and downloading files from a Cache Server
/// </summary>
public class Client
{
private enum StreamReadState
{
Response,
Size,
Id
}
private const int ProtocolVersion = 254;
private const string CmdTrxBegin = "ts";
private const string CmdTrxEnd = "te";
private const string CmdGet = "g";
private const string CmdPut = "p";
private const string CmdQuit = "q";
private const int ResponseLen = 2;
private const int SizeLen = 16;
private const int GuidLen = 16;
private const int HashLen = 16;
private const int IdLen = GuidLen + HashLen;
private const int ReadBufferLen = 64 * 1024;
private readonly Queue<IDownloadItem> m_downloadQueue;
private readonly TcpClient m_tcpClient;
private readonly string m_host;
private readonly int m_port;
internal Stream m_stream;
private Mutex m_mutex;
private readonly byte[] m_streamReadBuffer;
private int m_streamBytesRead;
private int m_streamBytesNeeded;
private StreamReadState m_streamReadState = StreamReadState.Response;
private DownloadFinishedEventArgs m_nextFileCompleteEventArgs;
private Stream m_nextWriteStream;
private bool m_inTrx;
/// <summary>
/// Returns the number of items in the download queue
/// </summary>
public int DownloadQueueLength
{
get { return m_downloadQueue.Count; }
}
/// <summary>
/// Event fired when a queued download request finishes.
/// </summary>
public event EventHandler<DownloadFinishedEventArgs> DownloadFinished;
/// <summary>
/// Remove all listeners from the DownloadFinished event
/// </summary>
public void ResetDownloadFinishedEventHandler()
{
DownloadFinished = null;
}
/// <summary>
/// Create a new Cache Server client
/// </summary>
/// <param name="host">The host name or IP of the Cache Server.</param>
/// <param name="port">The port number of the Cache Server. Default port is 8126.</param>
public Client(string host, int port = 8126)
{
m_streamReadBuffer = new byte[ReadBufferLen];
m_downloadQueue = new Queue<IDownloadItem>();
m_tcpClient = new TcpClient();
m_host = host;
m_port = port;
}
/// <summary>
/// Connects to the Cache Server and sends a protocol version handshake.
/// </summary>
/// <exception cref="Exception"></exception>
public void Connect()
{
var client = m_tcpClient;
client.Connect(m_host, m_port);
m_stream = client.GetStream();
m_stream.ReadTimeout = 10000;
m_stream.WriteTimeout = 10000;
SendVersion();
m_mutex = new Mutex();
}
/// <summary>
/// Connects to the Cache Server and sends a protocol version handshake. A TimeoutException is thrown if the connection cannot
/// be established within <paramref name="timeoutMs"/> milliseconds.
/// </summary>
/// <param name="timeoutMs"></param>
/// <exception cref="TimeoutException"></exception>
/// <exception cref="Exception"></exception>
public void Connect(int timeoutMs)
{
var client = m_tcpClient;
var op = client.BeginConnect(m_host, m_port, null, null);
var connected = op.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(timeoutMs));
if (!connected)
throw new TimeoutException();
m_stream = client.GetStream();
SendVersion();
m_mutex = new Mutex();
}
/// <summary>
/// Begin an upload transaction for an asset. Transactions in process can be interupted by calling BeginTransaction
/// again before calling EndTransaction.
/// </summary>
/// <param name="fileId"></param>
public void BeginTransaction(FileId fileId)
{
m_inTrx = true;
m_stream.Write(Encoding.ASCII.GetBytes(CmdTrxBegin), 0, 2);
m_stream.Write(fileId.guid, 0, GuidLen);
m_stream.Write(fileId.hash, 0, HashLen);
}
/// <summary>
/// Upload from the given stream for the given FileType. Will throw an exception if not preceeded by BeginTransaction.
/// </summary>
/// <param name="type"></param>
/// <param name="readStream"></param>
/// <exception cref="TransactionIsolationException"></exception>
/// <exception cref="ArgumentException"></exception>
public void Upload(FileType type, Stream readStream)
{
if (!m_inTrx)
throw new TransactionIsolationException("Upload without BeginTransaction");
if (!readStream.CanRead || !readStream.CanSeek)
throw new ArgumentException();
m_stream.Write(Encoding.ASCII.GetBytes(CmdPut + (char)type), 0, 2);
m_stream.Write(Util.EncodeInt64(readStream.Length), 0, SizeLen);
var buf = new byte[ReadBufferLen];
while (readStream.Position < readStream.Length - 1)
{
var len = readStream.Read(buf, 0, ReadBufferLen);
m_stream.Write(buf, 0, len);
}
}
/// <summary>
/// Commit the uploaded files to the Cache Server. Will throw an exception if not preceeded by BeginTransaction.
/// </summary>
/// <exception cref="TransactionIsolationException"></exception>
public void EndTransaction()
{
if (!m_inTrx)
throw new TransactionIsolationException("EndTransaction without BeginTransaction");
m_inTrx = false;
m_stream.Write(Encoding.ASCII.GetBytes(CmdTrxEnd), 0, 2);
}
/// <summary>
/// Send a download request to the Cache Server. Listen to the DownloadComplete event to read the results.
/// </summary>
/// <param name="downloadItem">The IDownloadItem that specifies which file to download</param>
public void QueueDownload(IDownloadItem downloadItem)
{
m_stream.Write(Encoding.ASCII.GetBytes(CmdGet + (char)downloadItem.Type), 0, 2);
m_stream.Write(downloadItem.Id.guid, 0, GuidLen);
m_stream.Write(downloadItem.Id.hash, 0, HashLen);
m_mutex.WaitOne();
m_downloadQueue.Enqueue(downloadItem);
int count = m_downloadQueue.Count;
m_mutex.ReleaseMutex();
if (count == 1)
ReadNextDownloadResult();
}
/// <summary>
/// Close the connection to the Cache Server. Sends the 'quit' command and closes the network stream.
/// </summary>
public void Close()
{
if (m_stream != null)
m_stream.Write(Encoding.ASCII.GetBytes(CmdQuit), 0, 1);
if (m_tcpClient != null)
m_tcpClient.Close();
if (m_mutex != null)
{
m_mutex.Dispose();
m_mutex = null;
}
}
private void SendVersion()
{
var encodedVersion = Util.EncodeInt32(ProtocolVersion, true);
m_stream.Write(encodedVersion, 0, encodedVersion.Length);
var versionBuf = new byte[8];
var pos = 0;
while (pos < versionBuf.Length - 1)
{
pos += m_stream.Read(versionBuf, 0, versionBuf.Length);
}
if (Util.ReadUInt32(versionBuf, 0) != ProtocolVersion)
throw new Exception("Server version mismatch");
}
private void OnDownloadFinished(DownloadFinishedEventArgs e)
{
m_mutex.WaitOne();
m_downloadQueue.Dequeue();
int count = m_downloadQueue.Count;
m_mutex.ReleaseMutex();
e.DownloadQueueLength = count;
if (DownloadFinished != null)
DownloadFinished(this, e);
if (count > 0)
ReadNextDownloadResult();
}
internal void ReadNextDownloadResult()
{
m_streamReadState = StreamReadState.Response;
m_streamBytesNeeded = ResponseLen;
m_streamBytesRead = 0;
m_nextFileCompleteEventArgs = new DownloadFinishedEventArgs { Result = DownloadResult.Failure };
BeginReadHeader();
}
private void BeginReadHeader()
{
m_stream.BeginRead(m_streamReadBuffer,
m_streamBytesRead,
m_streamBytesNeeded - m_streamBytesRead,
EndReadHeader,
m_stream);
}
internal Action<int, byte[]> OnReadHeader;
private void EndReadHeader(IAsyncResult r)
{
var bytesRead = m_stream.EndRead(r);
if (bytesRead <= 0) return;
m_streamBytesRead += bytesRead;
if (OnReadHeader != null)
OnReadHeader(m_streamBytesRead, m_streamReadBuffer);
if (m_streamBytesRead < m_streamBytesNeeded)
{
BeginReadHeader();
return;
}
switch (m_streamReadState)
{
case StreamReadState.Response:
if (Convert.ToChar(m_streamReadBuffer[0]) == '+')
{
m_streamReadState = StreamReadState.Size;
m_streamBytesNeeded = SizeLen;
}
else
{
m_nextFileCompleteEventArgs.Result = DownloadResult.FileNotFound;
m_streamReadState = StreamReadState.Id;
m_streamBytesNeeded = IdLen;
}
break;
case StreamReadState.Size:
m_nextFileCompleteEventArgs.Size = Util.ReadUInt64(m_streamReadBuffer, 0);
m_streamReadState = StreamReadState.Id;
m_streamBytesNeeded = IdLen;
break;
case StreamReadState.Id:
m_mutex.WaitOne();
var next = m_downloadQueue.Peek();
m_mutex.ReleaseMutex();
m_nextFileCompleteEventArgs.DownloadItem = next;
var match =
Util.ByteArraysAreEqual(next.Id.guid, 0, m_streamReadBuffer, 0, GuidLen) &&
Util.ByteArraysAreEqual(next.Id.hash, 0, m_streamReadBuffer, GuidLen, HashLen);
if (!match)
{
Close();
throw new InvalidDataException();
}
if (m_nextFileCompleteEventArgs.Result == DownloadResult.FileNotFound)
{
OnDownloadFinished(m_nextFileCompleteEventArgs);
}
else
{
var size = m_nextFileCompleteEventArgs.Size;
m_nextWriteStream = next.GetWriteStream(size);
m_streamBytesNeeded = (int)size;
m_streamBytesRead = 0;
BeginReadData();
}
return;
default:
throw new ArgumentOutOfRangeException();
}
m_streamBytesRead = 0;
BeginReadHeader();
}
private void BeginReadData()
{
var len = Math.Min(ReadBufferLen, m_streamBytesNeeded - m_streamBytesRead);
m_stream.BeginRead(m_streamReadBuffer, 0, len, EndReadData, null);
}
private void EndReadData(IAsyncResult readResult)
{
var bytesRead = m_stream.EndRead(readResult);
Debug.Assert(bytesRead > 0);
m_streamBytesRead += bytesRead;
var writeResult = m_nextWriteStream.BeginWrite(m_streamReadBuffer, 0, bytesRead, null, null);
m_nextWriteStream.EndWrite(writeResult);
if (m_streamBytesRead < m_streamBytesNeeded)
{
BeginReadData();
}
else
{
m_nextFileCompleteEventArgs.DownloadItem.Finish();
m_nextFileCompleteEventArgs.Result = DownloadResult.Success;
OnDownloadFinished(m_nextFileCompleteEventArgs);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 02aa99c7f5d8f4c77810419ebddad96d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,82 @@
using System;
using System.IO;
using UnityEngine;
namespace UnityEditor.Build.CacheServer
{
/// <summary>
/// IDownloadItem implementation for downloading to a file specified by path.
/// </summary>
public class FileDownloadItem : IDownloadItem
{
private Stream m_writeStream;
private string m_tmpPath;
/// <summary>
/// The FileId for the FileDownloadItem. FileId consists of an assets guid and hash code.
/// </summary>
public FileId Id { get; private set; }
/// <summary>
/// The type of the FileDownloadItems desired item.
/// </summary>
public FileType Type { get; private set; }
/// <summary>
/// File path where downloaded file data is saved. Data is first written to a temporary file location, and moved
/// into place when the Finish() method is called by the Cache Server Client.
/// </summary>
public string FilePath { get; private set; }
/// <summary>
/// Gets the write stream for a given FileDownloadItem. If one does not exist, it will be created.
/// </summary>
/// <param name="size"></param>
/// <returns></returns>
public Stream GetWriteStream(long size)
{
if (m_writeStream == null)
{
m_tmpPath = Path.GetTempFileName();
m_writeStream = new FileStream(m_tmpPath, FileMode.Create, FileAccess.Write);
}
return m_writeStream;
}
/// <summary>
/// Create a new FileDownloadItem
/// </summary>
/// <param name="fileId">The FileId of the desired item.</param>
/// <param name="fileType">The FileType of the desired item.</param>
/// <param name="path">The path of the desired item.</param>
public FileDownloadItem(FileId fileId, FileType fileType, string path)
{
Id = fileId;
Type = fileType;
FilePath = path;
}
/// <summary>
/// Dispose the FileDownloadItems write stream and move the data from the temporary path to its final destination.
/// </summary>
public void Finish()
{
if (m_writeStream == null)
return;
m_writeStream.Dispose();
try
{
if (File.Exists(FilePath))
File.Delete(FilePath);
File.Move(m_tmpPath, FilePath);
}
catch (Exception e)
{
Debug.LogError(e);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e253c6be7d9134114a9b533965c2b45f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,33 @@
using System.IO;
namespace UnityEditor.Build.CacheServer
{
/// <summary>
/// Represents a single file download request from a Cache Server.
/// </summary>
public interface IDownloadItem
{
/// <summary>
/// the FileId (guid/hash pair) of the file to download
/// </summary>
FileId Id { get; }
/// <summary>
/// the FileType for the given FileId to download
/// </summary>
FileType Type { get; }
/// <summary>
/// Provides a writable stream for saving downloaded file bytes
/// </summary>
/// <param name="size">Size of file to download</param>
/// <returns>A writable stream</returns>
Stream GetWriteStream(long size);
/// <summary>
/// Method called when a download is finished. Used to finalize and cleanup a single file download. e.g. to move
/// a temporary file into place.
/// </summary>
void Finish();
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 31314932afa7e4ce3ab642bfbc2dd8ff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,14 @@
{
"name": "UnityEditor.CacheServer",
"references": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": []
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 27fb11404301449859aa664f4cb020e0
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,163 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
[assembly: InternalsVisibleTo("UnityEditor.CacheServerTests")]
namespace UnityEditor.Build.CacheServer
{
internal static class Util
{
private const string IpAddressKey = "CacheServerIPAddress";
private static int ReverseByte(int b)
{
return ((b & 0x0F) << 4) | ((b >> 4) & 0x0F);
}
private static byte[] StringToByteArray(string input, bool asGuid)
{
var bytes = new byte[input.Length / 2];
for (var i = 0; i < input.Length; i += 2)
{
var b = Convert.ToByte(input.Substring(i, 2), 16);
bytes[i / 2] = asGuid ? (byte)ReverseByte(b) : b;
}
return bytes;
}
/// <summary>
/// Convert a hex string to a byte array that represents an Asset Hash
/// </summary>
/// <param name="hashStr">32 character hex string</param>
/// <returns>byte array</returns>
public static byte[] StringToHash(string hashStr)
{
Debug.Assert(hashStr.Length == 32);
return StringToByteArray(hashStr, false);
}
/// <summary>
/// Convert a hex string to a byte array that represents an Asset GUID
/// </summary>
/// <param name="guidStr">32 character hex string</param>
/// <returns>byte array</returns>
public static byte[] StringToGuid(string guidStr)
{
Debug.Assert(guidStr.Length == 32);
return StringToByteArray(guidStr, true);
}
/// <summary>
/// Parse an ascii byte array at <paramref name="index"/>start as an int value
/// </summary>
/// <param name="bytes">byte array</param>
/// <param name="index">offset</param>
/// <returns></returns>
public static int ReadUInt32(byte[] bytes, int index)
{
Debug.Assert(bytes.Length + index >= 8);
return Int32.Parse(Encoding.ASCII.GetString(bytes, index, 8), NumberStyles.HexNumber);
}
/// <summary>
/// Encode an integer as an ascii byte array
/// </summary>
/// <param name="input">integer</param>
/// <param name="minLength">true ensure the byte array is as short as possible; false to pad to 8 bytes</param>
/// <returns></returns>
public static byte[] EncodeInt32(int input, bool minLength = false)
{
return Encoding.ASCII.GetBytes(input.ToString(minLength ? "X" : "X8"));
}
/// <summary>
/// Parse a subset of an ascii byte array as a long value
/// </summary>
/// <param name="bytes">byte array</param>
/// <param name="index">offset within <paramref name="bytes"/> to read from</param>
/// <returns></returns>
public static long ReadUInt64(byte[] bytes, int index)
{
Debug.Assert(bytes.Length + index >= 16);
return Int64.Parse(Encoding.ASCII.GetString(bytes, index, 16), NumberStyles.HexNumber);
}
/// <summary>
/// Encode a long value into an ascii byte array
/// </summary>
/// <param name="input">long value</param>
/// <returns></returns>
public static byte[] EncodeInt64(long input)
{
return Encoding.ASCII.GetBytes(input.ToString("X16"));
}
/// <summary>
/// Compare two byte arrays for value equality
/// </summary>
/// <param name="ar1">first array</param>
/// <param name="ar2">second array</param>
/// <returns></returns>
public static bool ByteArraysAreEqual(byte[] ar1, byte[] ar2)
{
return ar1.Length == ar2.Length && ByteArraysAreEqual(ar1, 0, ar2, 0, ar1.Length);
}
/// <summary>
/// Compare two byte arrays for value equality at specific offsets and length
/// </summary>
/// <param name="ar1">first array</param>
/// <param name="start1">offset within first array</param>
/// <param name="ar2">second array</param>
/// <param name="start2">offset within second array</param>
/// <param name="count">number of bytes to compare</param>
/// <returns></returns>
public static bool ByteArraysAreEqual(byte[] ar1, int start1, byte[] ar2, int start2, int count)
{
Debug.Assert(start1 >= 0 && start2 >= 0 && count >= 0);
if (start1 + count > ar1.Length)
return false;
if (start2 + count > ar2.Length)
return false;
for (var i = 0; i < count; i++)
if (ar1[start1 + i] != ar2[start2 + i])
return false;
return true;
}
/// <summary>
/// Retrieve the configured cache server address for the Unity Editor
/// </summary>
public static string ConfigCacheServerAddress
{
get { return EditorPrefs.GetString(IpAddressKey); }
}
/// <summary>
/// Parse an address string in the format of 'address:port' to a string address and integer port number
/// </summary>
/// <param name="address">combined address string</param>
/// <param name="host">address part</param>
/// <param name="port">port part</param>
public static void ParseCacheServerIpAddress(string address, out string host, out int port)
{
host = null;
port = 8126;
var parts = address.Split(':');
if (parts.Length > 0)
host = parts[0];
if (parts.Length > 1)
port = int.Parse(parts[1]);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a0c7d049419ae46f19ead59b45d622fb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: