浏览代码
Merge pull request #14 from unity/multipeer
Merge pull request #14 from unity/multipeer
Use Multipeer Connectivity for collaborative sessions sample/3.1
GitHub Enterprise
5 年前
当前提交
c81f5e11
共有 53 个文件被更改,包括 1202 次插入 和 1725 次删除
-
811Assets/Scenes/ARCollaborationData/ARCollaborationDataExample.unity
-
141Assets/Scenes/ARCollaborationData/CollaborativeSession.cs
-
11Assets/Scenes/ARCollaborationData/CollaborativeSession.cs.meta
-
8Assets/Scripts/Multipeer.meta
-
100Assets/Scripts/Multipeer/MCSession.cs
-
11Assets/Scripts/Multipeer/MCSession.cs.meta
-
18Assets/Scripts/Multipeer/MCSessionSendDataMode.cs
-
11Assets/Scripts/Multipeer/MCSessionSendDataMode.cs.meta
-
68Assets/Scripts/Multipeer/NSData.cs
-
11Assets/Scripts/Multipeer/NSData.cs.meta
-
56Assets/Scripts/Multipeer/NSError.cs
-
11Assets/Scripts/Multipeer/NSError.cs.meta
-
18Assets/Scripts/Multipeer/NSErrorException.cs
-
11Assets/Scripts/Multipeer/NSErrorException.cs.meta
-
80Assets/Scripts/Multipeer/NSString.cs
-
11Assets/Scripts/Multipeer/NSString.cs.meta
-
17Assets/Scripts/Multipeer/NativeApi.cs
-
11Assets/Scripts/Multipeer/NativeApi.cs.meta
-
8Assets/Scripts/Multipeer/NativeCode.meta
-
37Assets/Scripts/Multipeer/NativeCode/MultipeerDelegate-C-Bridge.m.meta
-
13Assets/Scripts/Multipeer/NativeCode/MultipeerDelegate.h
-
27Assets/Scripts/Multipeer/NativeCode/MultipeerDelegate.h.meta
-
142Assets/Scripts/Multipeer/NativeCode/MultipeerDelegate.m
-
105Assets/Scripts/Multipeer/NativeCode/MultipeerDelegate.m.meta
-
30Assets/Scripts/Multipeer/NativeCode/NSData-C-Bridge.m
-
37Assets/Scripts/Multipeer/NativeCode/NSData-C-Bridge.m.meta
-
12Assets/Scripts/Multipeer/NativeCode/NSError-C-Bridge.m
-
37Assets/Scripts/Multipeer/NativeCode/NSError-C-Bridge.m.meta
-
52Assets/Scripts/Multipeer/NativeCode/NSString-C-Bridge.m
-
37Assets/Scripts/Multipeer/NativeCode/NSString-C-Bridge.m.meta
-
60Assets/Scripts/Multipeer/NativeCode/MultipeerDelegate-C-Bridge.m
-
103Assets/Scenes/ARCollaborationData/ClientServerSelector.cs
-
11Assets/Scenes/ARCollaborationData/ClientServerSelector.cs.meta
-
50Assets/Scenes/ARCollaborationData/TCPClient.cs
-
11Assets/Scenes/ARCollaborationData/TCPClient.cs.meta
-
315Assets/Scenes/ARCollaborationData/TCPConnection.cs
-
11Assets/Scenes/ARCollaborationData/TCPConnection.cs.meta
-
43Assets/Scenes/ARCollaborationData/TCPServer.cs
-
11Assets/Scenes/ARCollaborationData/TCPServer.cs.meta
-
4Assets/Scenes/ARCollaborationData/IMessage.cs
-
11Assets/Scenes/ARCollaborationData/IMessage.cs.meta
-
23Assets/Scenes/ARCollaborationData/MessageHeader.cs
-
11Assets/Scenes/ARCollaborationData/MessageHeader.cs.meta
-
6Assets/Scenes/ARCollaborationData/MessageType.cs
-
11Assets/Scenes/ARCollaborationData/MessageType.cs.meta
-
108Assets/Scenes/ARCollaborationData/NetworkBuffer.cs
-
11Assets/Scenes/ARCollaborationData/NetworkBuffer.cs.meta
-
86Assets/Scenes/ARCollaborationData/NetworkDataDecoder.cs
-
11Assets/Scenes/ARCollaborationData/NetworkDataDecoder.cs.meta
-
77Assets/Scenes/ARCollaborationData/NetworkDataEncoder.cs
-
11Assets/Scenes/ARCollaborationData/NetworkDataEncoder.cs.meta
|
|||
using UnityEngine; |
|||
using UnityEngine.XR.ARFoundation; |
|||
|
|||
#if UNITY_IOS && !UNITY_EDITOR
|
|||
using Unity.iOS.Multipeer; |
|||
using UnityEngine.XR.ARKit; |
|||
#endif
|
|||
|
|||
[RequireComponent(typeof(ARSession))] |
|||
public class CollaborativeSession : MonoBehaviour |
|||
{ |
|||
[SerializeField] |
|||
[Tooltip("The name for this network service. It should be 15 characters or less and can contain ASCII, lowercase letters, numbers, and hyphens.")] |
|||
string m_ServiceType; |
|||
|
|||
/// <summary>
|
|||
/// The name for this network service.
|
|||
/// See <a href="https://developer.apple.com/documentation/multipeerconnectivity/mcnearbyserviceadvertiser">MCNearbyServiceAdvertiser</a>
|
|||
/// for the purpose of and restrictions on this name.
|
|||
/// </summary>
|
|||
public string serviceType |
|||
{ |
|||
get { return m_ServiceType; } |
|||
set { m_ServiceType = value; } |
|||
} |
|||
|
|||
ARSession m_ARSession; |
|||
|
|||
void DisableNotSupported(string reason) |
|||
{ |
|||
enabled = false; |
|||
Logger.Log(reason); |
|||
} |
|||
|
|||
void OnEnable() |
|||
{ |
|||
#if UNITY_IOS && !UNITY_EDITOR
|
|||
var subsystem = GetSubsystem(); |
|||
if (!ARKitSessionSubsystem.supportsCollaboration || subsystem == null) |
|||
{ |
|||
DisableNotSupported("Collaborative sessions require iOS 13."); |
|||
return; |
|||
} |
|||
|
|||
subsystem.collaborationEnabled = true; |
|||
m_MCSession.Enabled = true; |
|||
#else
|
|||
DisableNotSupported("Collaborative sessions are an ARKit 3 feature; This platform does not support them."); |
|||
#endif
|
|||
} |
|||
|
|||
#if UNITY_IOS && !UNITY_EDITOR
|
|||
MCSession m_MCSession; |
|||
|
|||
ARKitSessionSubsystem GetSubsystem() |
|||
{ |
|||
if (m_ARSession == null) |
|||
return null; |
|||
|
|||
return m_ARSession.subsystem as ARKitSessionSubsystem; |
|||
} |
|||
|
|||
void Awake() |
|||
{ |
|||
m_ARSession = GetComponent<ARSession>(); |
|||
m_MCSession = new MCSession(SystemInfo.deviceName, m_ServiceType); |
|||
} |
|||
|
|||
void OnDisable() |
|||
{ |
|||
m_MCSession.Enabled = false; |
|||
|
|||
var subsystem = GetSubsystem(); |
|||
if (subsystem != null) |
|||
subsystem.collaborationEnabled = false; |
|||
} |
|||
|
|||
void Update() |
|||
{ |
|||
var subsystem = GetSubsystem(); |
|||
if (subsystem == null) |
|||
return; |
|||
|
|||
// Check for new collaboration data
|
|||
while (subsystem.collaborationDataCount > 0) |
|||
{ |
|||
using (var collaborationData = subsystem.DequeueCollaborationData()) |
|||
{ |
|||
CollaborationNetworkingIndicator.NotifyHasCollaborationData(); |
|||
|
|||
if (m_MCSession.ConnectedPeerCount == 0) |
|||
continue; |
|||
|
|||
using (var serializedData = collaborationData.ToSerialized()) |
|||
using (var data = NSData.CreateWithBytesNoCopy(serializedData.bytes)) |
|||
{ |
|||
m_MCSession.SendToAllPeers(data, collaborationData.priority == ARCollaborationDataPriority.Critical |
|||
? MCSessionSendDataMode.Reliable |
|||
: MCSessionSendDataMode.Unreliable); |
|||
|
|||
CollaborationNetworkingIndicator.NotifyOutgoingDataSent(); |
|||
|
|||
// Only log 'critical' data as 'optional' data tends to come every frame
|
|||
if (collaborationData.priority == ARCollaborationDataPriority.Critical) |
|||
{ |
|||
Logger.Log($"Sent {data.Length} bytes of collaboration data."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Check for incoming data
|
|||
while (m_MCSession.ReceivedDataQueueSize > 0) |
|||
{ |
|||
CollaborationNetworkingIndicator.NotifyIncomingDataReceived(); |
|||
|
|||
using (var data = m_MCSession.DequeueReceivedData()) |
|||
using (var collaborationData = new ARCollaborationData(data.Bytes)) |
|||
{ |
|||
if (collaborationData.valid) |
|||
{ |
|||
subsystem.UpdateWithCollaborationData(collaborationData); |
|||
if (collaborationData.priority == ARCollaborationDataPriority.Critical) |
|||
{ |
|||
Logger.Log($"Received {data.Bytes.Length} bytes of collaboration data."); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
Logger.Log($"Received {data.Bytes.Length} bytes from remote, but the collaboration data was not valid."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
void OnDestroy() |
|||
{ |
|||
m_MCSession.Dispose(); |
|||
} |
|||
#endif
|
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: a7c1d01ad3d754ef788a30c12b724743 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: ab5ca8171efd6403fa3e0f422eccde1e |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace Unity.iOS.Multipeer |
|||
{ |
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public struct MCSession : IDisposable, IEquatable<MCSession> |
|||
{ |
|||
IntPtr m_Ptr; |
|||
|
|||
public bool Created => m_Ptr != IntPtr.Zero; |
|||
|
|||
public bool Enabled |
|||
{ |
|||
get |
|||
{ |
|||
if (!Created) |
|||
return false; |
|||
return GetEnabled(this); |
|||
} |
|||
|
|||
set |
|||
{ |
|||
if (!Created && value) |
|||
throw new InvalidOperationException(); |
|||
SetEnabled(this, value); |
|||
} |
|||
} |
|||
|
|||
public MCSession(string peerName, string serviceType) |
|||
{ |
|||
if (peerName == null) |
|||
throw new ArgumentNullException(nameof(peerName)); |
|||
|
|||
if (serviceType == null) |
|||
throw new ArgumentNullException(nameof(serviceType)); |
|||
|
|||
using (var peerName_NSString = new NSString(peerName)) |
|||
using (var serviceType_NSString = new NSString(serviceType)) |
|||
{ |
|||
m_Ptr = InitWithName(peerName_NSString, serviceType_NSString); |
|||
} |
|||
} |
|||
|
|||
public void SendToAllPeers(NSData data, MCSessionSendDataMode mode) |
|||
{ |
|||
if (!Created) |
|||
throw new InvalidOperationException($"The {typeof(MCSession).Name} has not been created."); |
|||
|
|||
if (!data.Created) |
|||
throw new ArgumentException($"'{nameof(data)}' is not valid.", nameof(data)); |
|||
|
|||
using (var error = SendToAllPeers(this, data, mode)) |
|||
{ |
|||
if (error.Valid) |
|||
throw error.ToException(); |
|||
} |
|||
} |
|||
|
|||
public int ReceivedDataQueueSize => GetReceivedDataQueueSize(this); |
|||
|
|||
public NSData DequeueReceivedData() |
|||
{ |
|||
if (!Created) |
|||
throw new InvalidOperationException($"The {typeof(MCSession).Name} has not been created."); |
|||
|
|||
return DequeueReceivedData(this); |
|||
} |
|||
|
|||
public int ConnectedPeerCount => GetConnectedPeerCount(this); |
|||
|
|||
public void Dispose() => NativeApi.CFRelease(ref m_Ptr); |
|||
public override int GetHashCode() => m_Ptr.GetHashCode(); |
|||
public override bool Equals(object obj) => (obj is MCSession) && Equals((MCSession)obj); |
|||
public bool Equals(MCSession other) => m_Ptr == other.m_Ptr; |
|||
public static bool operator==(MCSession lhs, MCSession rhs) => lhs.Equals(rhs); |
|||
public static bool operator!=(MCSession lhs, MCSession rhs) => !lhs.Equals(rhs); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_Delegate_sendToAllPeers")] |
|||
static extern NSError SendToAllPeers(MCSession self, NSData data, MCSessionSendDataMode mode); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_Delegate_initWithName")] |
|||
static extern IntPtr InitWithName(NSString name, NSString serviceType); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_Delegate_receivedDataQueueSize")] |
|||
static extern int GetReceivedDataQueueSize(MCSession self); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_Delegate_dequeueReceivedData")] |
|||
static extern NSData DequeueReceivedData(MCSession self); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_Delegate_connectedPeerCount")] |
|||
static extern int GetConnectedPeerCount(MCSession self); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_Delegate_setEnabled")] |
|||
static extern void SetEnabled(MCSession self, bool enabled); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_Delegate_getEnabled")] |
|||
static extern bool GetEnabled(MCSession self); |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: f0dd3cf9695cb42f7b9ffa20d3a59011 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
namespace Unity.iOS.Multipeer |
|||
{ |
|||
/// <summary>
|
|||
/// MCSession send modes
|
|||
/// </summary>
|
|||
public enum MCSessionSendDataMode |
|||
{ |
|||
/// <summary>
|
|||
/// Guaranteed reliable and in-order delivery.
|
|||
/// </summary>
|
|||
Reliable, |
|||
|
|||
/// <summary>
|
|||
/// Sent immediately without queuing, no guaranteed delivery.
|
|||
/// </summary>
|
|||
Unreliable |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 5673d45a5dae34743841658416feef52 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using Unity.Collections; |
|||
using Unity.Collections.LowLevel.Unsafe; |
|||
|
|||
namespace Unity.iOS.Multipeer |
|||
{ |
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public struct NSData : IDisposable, IEquatable<NSData> |
|||
{ |
|||
IntPtr m_Ptr; |
|||
|
|||
internal NSData(IntPtr existing) => m_Ptr = existing; |
|||
|
|||
public bool Created => m_Ptr != IntPtr.Zero; |
|||
|
|||
public int Length => Created ? GetLength(this) : 0; |
|||
|
|||
public static unsafe NSData CreateWithBytes(NativeSlice<byte> bytes) |
|||
{ |
|||
var ptr = bytes.GetUnsafePtr(); |
|||
if (ptr == null) |
|||
throw new ArgumentException($"The {typeof(NativeSlice<byte>).Name} is not valid.", nameof(bytes)); |
|||
|
|||
return new NSData(CreateWithBytes(ptr, bytes.Length)); |
|||
} |
|||
|
|||
public static unsafe NSData CreateWithBytesNoCopy(NativeSlice<byte> bytes) |
|||
{ |
|||
var ptr = bytes.GetUnsafePtr(); |
|||
if (ptr == null) |
|||
throw new ArgumentException($"The {typeof(NativeSlice<byte>).Name} is not valid.", nameof(bytes)); |
|||
|
|||
return new NSData(CreateWithBytesNoCopy(ptr, bytes.Length, false)); |
|||
} |
|||
|
|||
public unsafe NativeSlice<byte> Bytes |
|||
{ |
|||
get |
|||
{ |
|||
if (!Created) |
|||
throw new InvalidOperationException($"The {typeof(NSData).Name} has not been created."); |
|||
|
|||
return NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice<byte>(GetBytes(this), 1, GetLength(this)); |
|||
} |
|||
} |
|||
|
|||
public void Dispose() => NativeApi.CFRelease(ref m_Ptr); |
|||
|
|||
public override int GetHashCode() => m_Ptr.GetHashCode(); |
|||
public override bool Equals(object obj) => (obj is NSData) && Equals((NSData)obj); |
|||
public bool Equals(NSData other) => m_Ptr == other.m_Ptr; |
|||
public static bool operator==(NSData lhs, NSData rhs) => lhs.Equals(rhs); |
|||
public static bool operator!=(NSData lhs, NSData rhs) => !lhs.Equals(rhs); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_NSData_getLength")] |
|||
static extern int GetLength(NSData self); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_NSData_getBytes")] |
|||
static extern unsafe void* GetBytes(NSData self); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_NSData_createWithBytes")] |
|||
static extern unsafe IntPtr CreateWithBytes(void* bytes, int length); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_NSData_createWithBytesNoCopy")] |
|||
static extern unsafe IntPtr CreateWithBytesNoCopy(void* bytes, int length, bool freeWhenDone); |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 0aac506fdc0254d79b2479e6735d7ba3 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace Unity.iOS.Multipeer |
|||
{ |
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public struct NSError : IDisposable, IEquatable<NSError> |
|||
{ |
|||
IntPtr m_Ptr; |
|||
|
|||
public bool Valid => m_Ptr != IntPtr.Zero; |
|||
|
|||
public NSErrorException ToException() |
|||
{ |
|||
return new NSErrorException(Code, Description); |
|||
} |
|||
|
|||
public long Code |
|||
{ |
|||
get |
|||
{ |
|||
if (!Valid) |
|||
throw new InvalidOperationException($"The {typeof(NSError).Name} is not valid."); |
|||
|
|||
return GetCode(this); |
|||
} |
|||
} |
|||
|
|||
public string Description |
|||
{ |
|||
get |
|||
{ |
|||
if (!Valid) |
|||
throw new InvalidOperationException($"The {typeof(NSError).Name} is not valid."); |
|||
|
|||
using (var description = GetLocalizedDescription(this)) |
|||
{ |
|||
return description.ToString(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void Dispose() => NativeApi.CFRelease(ref m_Ptr); |
|||
public override int GetHashCode() => m_Ptr.GetHashCode(); |
|||
public override bool Equals(object obj) => (obj is NSError) && Equals((NSError)obj); |
|||
public bool Equals(NSError other) => m_Ptr == other.m_Ptr; |
|||
public static bool operator==(NSError lhs, NSError rhs) => lhs.Equals(rhs); |
|||
public static bool operator!=(NSError lhs, NSError rhs) => !lhs.Equals(rhs); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_NSError_code")] |
|||
static extern long GetCode(NSError error); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_NSError_localizedDescription")] |
|||
static extern NSString GetLocalizedDescription(NSError error); |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 10a04c15626774337abc28e3ed0dc046 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
|
|||
namespace Unity.iOS.Multipeer |
|||
{ |
|||
public class NSErrorException : Exception |
|||
{ |
|||
public NSErrorException(long code, string description) |
|||
: base($"NSError {code}: {description}") |
|||
{ |
|||
Code = code; |
|||
Description = description; |
|||
} |
|||
|
|||
public long Code { get; private set; } |
|||
|
|||
public string Description { get; private set; } |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 491dc7d106d7642499e0ccfe7d11bf70 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using Unity.Collections; |
|||
using Unity.Collections.LowLevel.Unsafe; |
|||
|
|||
namespace Unity.iOS.Multipeer |
|||
{ |
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public struct NSString : IDisposable, IEquatable<NSString> |
|||
{ |
|||
IntPtr m_Ptr; |
|||
|
|||
internal NSString(IntPtr existing) => m_Ptr = existing; |
|||
|
|||
public NSString(string text) => m_Ptr = CreateWithString(text, text.Length); |
|||
|
|||
public NSString(NSData serializedString) |
|||
{ |
|||
if (!serializedString.Created) |
|||
throw new ArgumentException("The serialized string is not valid.", nameof(serializedString)); |
|||
|
|||
m_Ptr = Deserialize(serializedString); |
|||
} |
|||
|
|||
public bool Created => m_Ptr != IntPtr.Zero; |
|||
|
|||
public int Length => GetLength(this); |
|||
|
|||
public override unsafe string ToString() |
|||
{ |
|||
if (!Created) |
|||
return string.Empty; |
|||
|
|||
using (var buffer = new NativeArray<byte>(GetLengthOfBytes(this), Allocator.TempJob, NativeArrayOptions.UninitializedMemory)) |
|||
{ |
|||
if (GetBytes(this, buffer.GetUnsafePtr(), buffer.Length)) |
|||
{ |
|||
return Marshal.PtrToStringUni(new IntPtr(buffer.GetUnsafePtr()), Length); |
|||
} |
|||
else |
|||
{ |
|||
return string.Empty; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public NSData Serialize() |
|||
{ |
|||
if (!Created) |
|||
throw new InvalidOperationException($"The {typeof(NSString).Name} has not been created."); |
|||
|
|||
return Serialize(this); |
|||
} |
|||
|
|||
public void Dispose() => NativeApi.CFRelease(ref m_Ptr); |
|||
public override int GetHashCode() => m_Ptr.GetHashCode(); |
|||
public override bool Equals(object obj) => (obj is NSString) && Equals((NSString)obj); |
|||
public bool Equals(NSString other) => m_Ptr == other.m_Ptr; |
|||
public static bool operator==(NSString lhs, NSString rhs) => lhs.Equals(rhs); |
|||
public static bool operator!=(NSString lhs, NSString rhs) => !lhs.Equals(rhs); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_NSString_createWithString")] |
|||
static extern IntPtr CreateWithString([MarshalAs(UnmanagedType.LPWStr)] string text, int length); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_NSString_lengthOfBytesUsingEncoding")] |
|||
static extern int GetLengthOfBytes(NSString self); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_NSString_getLength")] |
|||
static extern int GetLength(NSString self); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_NSString_getBytes")] |
|||
static extern unsafe bool GetBytes(NSString self, void* buffer, int length); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_NSString_serialize")] |
|||
static extern NSData Serialize(NSString self); |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_NSString_deserialize")] |
|||
static extern IntPtr Deserialize(NSData data); |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 2a2bbe15074f94af4b35f25e435174f6 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace Unity.iOS.Multipeer |
|||
{ |
|||
internal static class NativeApi |
|||
{ |
|||
public static void CFRelease(ref IntPtr ptr) |
|||
{ |
|||
CFRelease(ptr); |
|||
ptr = IntPtr.Zero; |
|||
} |
|||
|
|||
[DllImport("__Internal", EntryPoint="UnityMC_CFRelease")] |
|||
public static extern void CFRelease(IntPtr ptr); |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 06e6b03cb95994416a492630b1c74fd0 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 841a08ffbdce6452182dcbfc65a3054c |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: f94297f07eee14b96b55b6ed49779df0 |
|||
PluginImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
iconMap: {} |
|||
executionOrder: {} |
|||
defineConstraints: [] |
|||
isPreloaded: 0 |
|||
isOverridable: 0 |
|||
isExplicitlyReferenced: 0 |
|||
validateReferences: 1 |
|||
platformData: |
|||
- first: |
|||
Any: |
|||
second: |
|||
enabled: 0 |
|||
settings: {} |
|||
- first: |
|||
Editor: Editor |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
DefaultValueInitialized: true |
|||
- first: |
|||
iPhone: iOS |
|||
second: |
|||
enabled: 1 |
|||
settings: {} |
|||
- first: |
|||
tvOS: tvOS |
|||
second: |
|||
enabled: 1 |
|||
settings: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
#import <MultipeerConnectivity/MultipeerConnectivity.h> |
|||
|
|||
@interface MultipeerDelegate : NSObject<MCSessionDelegate, MCNearbyServiceAdvertiserDelegate, MCNearbyServiceBrowserDelegate> |
|||
|
|||
- (nullable instancetype)initWithName:(nonnull NSString *)name serviceType:(nonnull NSString*)serviceType; |
|||
- (nullable NSError*)sendToAllPeers:(nonnull NSData*)data withMode:(MCSessionSendDataMode)mode; |
|||
- (NSUInteger)connectedPeerCount; |
|||
- (NSUInteger)queueSize; |
|||
- (nonnull NSData*)dequeue; |
|||
|
|||
@property BOOL enabled; |
|||
|
|||
@end |
|
|||
fileFormatVersion: 2 |
|||
guid: 598b4c4bd189e49b4b17a87ba0cf5418 |
|||
PluginImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
iconMap: {} |
|||
executionOrder: {} |
|||
defineConstraints: [] |
|||
isPreloaded: 0 |
|||
isOverridable: 0 |
|||
isExplicitlyReferenced: 0 |
|||
validateReferences: 1 |
|||
platformData: |
|||
- first: |
|||
Any: |
|||
second: |
|||
enabled: 1 |
|||
settings: {} |
|||
- first: |
|||
Editor: Editor |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
DefaultValueInitialized: true |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
#import "MultipeerDelegate.h" |
|||
|
|||
@implementation MultipeerDelegate |
|||
|
|||
MCSession* m_Session; |
|||
MCPeerID* m_PeerID; |
|||
NSMutableArray* m_Queue; |
|||
MCNearbyServiceAdvertiser* m_ServiceAdvertiser; |
|||
MCNearbyServiceBrowser* m_ServiceBrowser; |
|||
BOOL m_Enabled; |
|||
|
|||
- (instancetype)initWithName:(nonnull NSString *)name serviceType:(nonnull NSString *)serviceType |
|||
{ |
|||
if (self = [super init]) |
|||
{ |
|||
m_Enabled = false; |
|||
m_Queue = [[NSMutableArray alloc] init]; |
|||
m_PeerID = [[MCPeerID alloc] initWithDisplayName: name]; |
|||
m_Session = [[MCSession alloc] initWithPeer:m_PeerID |
|||
securityIdentity:nil |
|||
encryptionPreference:MCEncryptionRequired]; |
|||
m_Session.delegate = self; |
|||
|
|||
m_ServiceAdvertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:m_PeerID |
|||
discoveryInfo:nil |
|||
serviceType:serviceType]; |
|||
m_ServiceAdvertiser.delegate = self; |
|||
|
|||
m_ServiceBrowser = [[MCNearbyServiceBrowser alloc] initWithPeer:m_PeerID |
|||
serviceType:serviceType]; |
|||
m_ServiceBrowser.delegate = self; |
|||
} |
|||
|
|||
return self; |
|||
} |
|||
|
|||
- (BOOL)enabled |
|||
{ |
|||
return m_Enabled; |
|||
} |
|||
|
|||
- (void)setEnabled:(BOOL)enabled |
|||
{ |
|||
if (enabled) |
|||
{ |
|||
[m_ServiceAdvertiser startAdvertisingPeer]; |
|||
[m_ServiceBrowser startBrowsingForPeers]; |
|||
} |
|||
else |
|||
{ |
|||
[m_ServiceAdvertiser stopAdvertisingPeer]; |
|||
[m_ServiceBrowser stopBrowsingForPeers]; |
|||
@synchronized (m_Queue) |
|||
{ |
|||
[m_Queue removeAllObjects]; |
|||
} |
|||
} |
|||
|
|||
m_Enabled = enabled; |
|||
} |
|||
|
|||
- (NSError*)sendToAllPeers:(nonnull NSData*)data withMode:(MCSessionSendDataMode)mode |
|||
{ |
|||
if (m_Session.connectedPeers.count == 0) |
|||
return nil; |
|||
|
|||
NSError* error = nil; |
|||
[m_Session sendData:data |
|||
toPeers:m_Session.connectedPeers |
|||
withMode:mode |
|||
error:&error]; |
|||
|
|||
return error; |
|||
} |
|||
|
|||
- (NSUInteger)queueSize |
|||
{ |
|||
@synchronized (m_Queue) |
|||
{ |
|||
return m_Queue.count; |
|||
} |
|||
} |
|||
|
|||
- (nonnull NSData*)dequeue |
|||
{ |
|||
@synchronized (m_Queue) |
|||
{ |
|||
NSData* data = [m_Queue objectAtIndex:0]; |
|||
[m_Queue removeObjectAtIndex:0]; |
|||
return data; |
|||
} |
|||
} |
|||
|
|||
- (NSUInteger)connectedPeerCount |
|||
{ |
|||
return m_Session.connectedPeers.count; |
|||
} |
|||
|
|||
- (void)session:(nonnull MCSession *)session didFinishReceivingResourceWithName:(nonnull NSString *)resourceName fromPeer:(nonnull MCPeerID *)peerID atURL:(nullable NSURL *)localURL withError:(nullable NSError *)error { |
|||
// Not used. |
|||
} |
|||
|
|||
- (void)session:(nonnull MCSession *)session didReceiveData:(nonnull NSData *)data fromPeer:(nonnull MCPeerID *)peerID |
|||
{ |
|||
@synchronized (m_Queue) |
|||
{ |
|||
[m_Queue addObject:data]; |
|||
} |
|||
} |
|||
|
|||
- (void)session:(nonnull MCSession *)session didReceiveStream:(nonnull NSInputStream *)stream withName:(nonnull NSString *)streamName fromPeer:(nonnull MCPeerID *)peerID { |
|||
// Not used. |
|||
} |
|||
|
|||
- (void)session:(nonnull MCSession *)session didStartReceivingResourceWithName:(nonnull NSString *)resourceName fromPeer:(nonnull MCPeerID *)peerID withProgress:(nonnull NSProgress *)progress { |
|||
// Not used. |
|||
} |
|||
|
|||
- (void)session:(nonnull MCSession *)session peer:(nonnull MCPeerID *)peerID didChangeState:(MCSessionState)state { |
|||
// Not used. |
|||
} |
|||
|
|||
- (void)advertiser:(nonnull MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(nonnull MCPeerID *)peerID withContext:(nullable NSData *)context invitationHandler:(nonnull void (^)(BOOL, MCSession * _Nullable))invitationHandler |
|||
{ |
|||
invitationHandler(YES, m_Session); |
|||
} |
|||
|
|||
- (void)browser:(nonnull MCNearbyServiceBrowser *)browser foundPeer:(nonnull MCPeerID *)peerID withDiscoveryInfo:(nullable NSDictionary<NSString *,NSString *> *)info |
|||
{ |
|||
// Invite the peer to join our session |
|||
[browser invitePeer:peerID |
|||
toSession:m_Session |
|||
withContext:nil |
|||
timeout:10]; |
|||
} |
|||
|
|||
- (void)browser:(nonnull MCNearbyServiceBrowser *)browser lostPeer:(nonnull MCPeerID *)peerID |
|||
{ |
|||
// Not used |
|||
} |
|||
|
|||
@end |
|
|||
fileFormatVersion: 2 |
|||
guid: cba1a95445f3e40ac8bbf00450e84734 |
|||
PluginImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
iconMap: {} |
|||
executionOrder: {} |
|||
defineConstraints: [] |
|||
isPreloaded: 0 |
|||
isOverridable: 0 |
|||
isExplicitlyReferenced: 0 |
|||
validateReferences: 1 |
|||
platformData: |
|||
- first: |
|||
'': Any |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
Exclude Android: 1 |
|||
Exclude Editor: 1 |
|||
Exclude Linux: 1 |
|||
Exclude Linux64: 1 |
|||
Exclude LinuxUniversal: 1 |
|||
Exclude Lumin: 1 |
|||
Exclude OSXUniversal: 1 |
|||
Exclude Win: 1 |
|||
Exclude Win64: 1 |
|||
Exclude iOS: 0 |
|||
- first: |
|||
Android: Android |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
CPU: ARMv7 |
|||
- first: |
|||
Any: |
|||
second: |
|||
enabled: 0 |
|||
settings: {} |
|||
- first: |
|||
Editor: Editor |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
CPU: AnyCPU |
|||
DefaultValueInitialized: true |
|||
OS: AnyOS |
|||
- first: |
|||
Facebook: Win |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
CPU: AnyCPU |
|||
- first: |
|||
Facebook: Win64 |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
CPU: AnyCPU |
|||
- first: |
|||
Standalone: Linux |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
CPU: x86 |
|||
- first: |
|||
Standalone: Linux64 |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
CPU: x86_64 |
|||
- first: |
|||
Standalone: OSXUniversal |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
CPU: AnyCPU |
|||
- first: |
|||
Standalone: Win |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
CPU: AnyCPU |
|||
- first: |
|||
Standalone: Win64 |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
CPU: AnyCPU |
|||
- first: |
|||
iPhone: iOS |
|||
second: |
|||
enabled: 1 |
|||
settings: |
|||
AddToEmbeddedBinaries: false |
|||
CompileFlags: |
|||
FrameworkDependencies: MultipeerConnectivity; |
|||
- first: |
|||
tvOS: tvOS |
|||
second: |
|||
enabled: 1 |
|||
settings: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
#import <Foundation/Foundation.h> |
|||
|
|||
int UnityMC_NSData_getLength(void* self) |
|||
{ |
|||
NSData* data = (__bridge NSData*)self; |
|||
return (int)data.length; |
|||
} |
|||
|
|||
void* UnityMC_NSData_createWithBytes(void* bytes, int length) |
|||
{ |
|||
NSData* data = [[NSData alloc] initWithBytes:bytes |
|||
length:length]; |
|||
|
|||
return (__bridge_retained void*)data; |
|||
} |
|||
|
|||
void* UnityMC_NSData_createWithBytesNoCopy(void* bytes, int length, bool freeWhenDone) |
|||
{ |
|||
NSData* data = [[NSData alloc] initWithBytesNoCopy:bytes |
|||
length:length |
|||
freeWhenDone:freeWhenDone]; |
|||
|
|||
return (__bridge_retained void*)data; |
|||
} |
|||
|
|||
const void* UnityMC_NSData_getBytes(void* self) |
|||
{ |
|||
NSData* data = (__bridge NSData*)self; |
|||
return data.bytes; |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: a452ea1f1d87e4256a5af3b742f78e18 |
|||
PluginImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
iconMap: {} |
|||
executionOrder: {} |
|||
defineConstraints: [] |
|||
isPreloaded: 0 |
|||
isOverridable: 0 |
|||
isExplicitlyReferenced: 0 |
|||
validateReferences: 1 |
|||
platformData: |
|||
- first: |
|||
Any: |
|||
second: |
|||
enabled: 0 |
|||
settings: {} |
|||
- first: |
|||
Editor: Editor |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
DefaultValueInitialized: true |
|||
- first: |
|||
iPhone: iOS |
|||
second: |
|||
enabled: 1 |
|||
settings: {} |
|||
- first: |
|||
tvOS: tvOS |
|||
second: |
|||
enabled: 1 |
|||
settings: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
#import <Foundation/Foundation.h> |
|||
|
|||
long UnityMC_NSError_code(void* ptr) |
|||
{ |
|||
return ((__bridge NSError*)ptr).code; |
|||
} |
|||
|
|||
void* UnityMC_NSError_localizedDescription(void* ptr) |
|||
{ |
|||
NSString* desc = ((__bridge NSError*)ptr).localizedDescription; |
|||
return (__bridge_retained void*)desc; |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: a2cf20442c9d3412f9b1978da0184a7f |
|||
PluginImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
iconMap: {} |
|||
executionOrder: {} |
|||
defineConstraints: [] |
|||
isPreloaded: 0 |
|||
isOverridable: 0 |
|||
isExplicitlyReferenced: 0 |
|||
validateReferences: 1 |
|||
platformData: |
|||
- first: |
|||
Any: |
|||
second: |
|||
enabled: 0 |
|||
settings: {} |
|||
- first: |
|||
Editor: Editor |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
DefaultValueInitialized: true |
|||
- first: |
|||
iPhone: iOS |
|||
second: |
|||
enabled: 1 |
|||
settings: {} |
|||
- first: |
|||
tvOS: tvOS |
|||
second: |
|||
enabled: 1 |
|||
settings: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
#import <Foundation/Foundation.h> |
|||
|
|||
int UnityMC_NSString_lengthOfBytesUsingEncoding(void* self) |
|||
{ |
|||
if (self == NULL) |
|||
return 0; |
|||
|
|||
NSString* string = (__bridge NSString*)self; |
|||
return (int)[string lengthOfBytesUsingEncoding:NSUTF16LittleEndianStringEncoding]; |
|||
} |
|||
|
|||
bool UnityMC_NSString_getBytes(void* self, void* buffer, int length) |
|||
{ |
|||
NSString* string = (__bridge NSString*)self; |
|||
const NSRange range = NSMakeRange(0, string.length); |
|||
return [string getBytes:buffer |
|||
maxLength:length |
|||
usedLength:NULL |
|||
encoding:NSUTF16LittleEndianStringEncoding |
|||
options:0 |
|||
range:range |
|||
remainingRange:NULL]; |
|||
} |
|||
|
|||
int UnityMC_NSString_getLength(void* self) |
|||
{ |
|||
NSString* string = (__bridge NSString*)self; |
|||
return (int)string.length; |
|||
} |
|||
|
|||
void* UnityMC_NSString_createWithString(void* bytes, int length) |
|||
{ |
|||
NSString* string = [[NSString alloc] initWithBytes: bytes |
|||
length: 2 * length |
|||
encoding: NSUTF16LittleEndianStringEncoding]; |
|||
|
|||
return (__bridge_retained void*)string; |
|||
} |
|||
|
|||
void* UnityMC_NSString_serialize(void* self) |
|||
{ |
|||
NSString* string = (__bridge NSString*)self; |
|||
NSData* data = [NSKeyedArchiver archivedDataWithRootObject:string]; |
|||
return (__bridge_retained void*)data; |
|||
} |
|||
|
|||
void* UnityMC_NSString_deserialize(void* serializedString) |
|||
{ |
|||
NSData* data = (__bridge NSData*)serializedString; |
|||
NSString* string = [NSKeyedUnarchiver unarchiveObjectWithData:data]; |
|||
return (__bridge_retained void*)string; |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: f45e73c2d697643c898c3f259d718667 |
|||
PluginImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
iconMap: {} |
|||
executionOrder: {} |
|||
defineConstraints: [] |
|||
isPreloaded: 0 |
|||
isOverridable: 0 |
|||
isExplicitlyReferenced: 0 |
|||
validateReferences: 1 |
|||
platformData: |
|||
- first: |
|||
Any: |
|||
second: |
|||
enabled: 0 |
|||
settings: {} |
|||
- first: |
|||
Editor: Editor |
|||
second: |
|||
enabled: 0 |
|||
settings: |
|||
DefaultValueInitialized: true |
|||
- first: |
|||
iPhone: iOS |
|||
second: |
|||
enabled: 1 |
|||
settings: {} |
|||
- first: |
|||
tvOS: tvOS |
|||
second: |
|||
enabled: 1 |
|||
settings: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
#include "MultipeerDelegate.h" |
|||
|
|||
typedef void* ManagedMultipeerDelegate; |
|||
typedef void* ManagedNSError; |
|||
|
|||
ManagedMultipeerDelegate UnityMC_Delegate_initWithName(void* name, void* serviceType) |
|||
{ |
|||
MultipeerDelegate* delegate = [[MultipeerDelegate alloc] initWithName:(__bridge NSString*)name |
|||
serviceType:(__bridge NSString*)serviceType]; |
|||
return (__bridge_retained void*)delegate; |
|||
} |
|||
|
|||
ManagedNSError UnityMC_Delegate_sendToAllPeers(void* self, void* nsdata, int length, int mode) |
|||
{ |
|||
NSData* data = (__bridge NSData*)nsdata; |
|||
MultipeerDelegate* delegate = (__bridge MultipeerDelegate*)self; |
|||
NSError* error = [delegate sendToAllPeers:data withMode:(MCSessionSendDataMode)mode]; |
|||
return (__bridge_retained void*)error; |
|||
} |
|||
|
|||
int UnityMC_Delegate_receivedDataQueueSize(void* self) |
|||
{ |
|||
if (self == NULL) |
|||
return 0; |
|||
|
|||
MultipeerDelegate* delegate = (__bridge MultipeerDelegate*)self; |
|||
return (int)delegate.queueSize; |
|||
} |
|||
|
|||
void* UnityMC_Delegate_dequeueReceivedData(void* self) |
|||
{ |
|||
MultipeerDelegate* delegate = (__bridge MultipeerDelegate*)self; |
|||
return (__bridge_retained void*)delegate.dequeue; |
|||
} |
|||
|
|||
int UnityMC_Delegate_connectedPeerCount(void* self) |
|||
{ |
|||
MultipeerDelegate* delegate = (__bridge MultipeerDelegate*)self; |
|||
return (int)delegate.connectedPeerCount; |
|||
} |
|||
|
|||
void UnityMC_Delegate_setEnabled(void* self, bool enabled) |
|||
{ |
|||
MultipeerDelegate* delegate = (__bridge MultipeerDelegate*)self; |
|||
delegate.enabled = enabled; |
|||
} |
|||
|
|||
bool UnityMC_Delegate_getEnabled(void* self) |
|||
{ |
|||
MultipeerDelegate* delegate = (__bridge MultipeerDelegate*)self; |
|||
return delegate.enabled; |
|||
} |
|||
|
|||
void UnityMC_CFRelease(void* ptr) |
|||
{ |
|||
if (ptr) |
|||
{ |
|||
CFRelease(ptr); |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using UnityEngine; |
|||
using UnityEngine.UI; |
|||
|
|||
[RequireComponent(typeof(TCPClient))] |
|||
[RequireComponent(typeof(TCPServer))] |
|||
public class ClientServerSelector : MonoBehaviour |
|||
{ |
|||
[SerializeField] |
|||
Button m_JoinButton; |
|||
|
|||
public Button joinButton |
|||
{ |
|||
get { return m_JoinButton; } |
|||
set { m_JoinButton = value; } |
|||
} |
|||
|
|||
[SerializeField] |
|||
Button m_HostButton; |
|||
|
|||
public Button hostButton |
|||
{ |
|||
get { return m_HostButton; } |
|||
set { m_HostButton = value; } |
|||
} |
|||
|
|||
[SerializeField] |
|||
InputField m_IPAddressField; |
|||
|
|||
public InputField ipAddressField |
|||
{ |
|||
get { return m_IPAddressField; } |
|||
set { m_IPAddressField = value; } |
|||
} |
|||
|
|||
public void Join() |
|||
{ |
|||
var client = GetComponent<TCPClient>(); |
|||
var ipAddress = m_IPAddressField.text; |
|||
try |
|||
{ |
|||
File.WriteAllText(GetIPAddressPath(), ipAddress); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Logger.Log($"Could not save IP address because {e.ToString()}"); |
|||
} |
|||
|
|||
client.serverIP = ipAddress; |
|||
client.enabled = true; |
|||
enabled = false; |
|||
} |
|||
|
|||
public void Host() |
|||
{ |
|||
GetComponent<TCPClient>().enabled = false; |
|||
GetComponent<TCPServer>().enabled = true; |
|||
enabled = false; |
|||
} |
|||
|
|||
string GetIPAddressPath() |
|||
{ |
|||
return Path.Combine(Application.persistentDataPath, "ipaddress.txt"); |
|||
} |
|||
|
|||
void OnEnable() |
|||
{ |
|||
if (File.Exists(GetIPAddressPath())) |
|||
{ |
|||
var storedIPAddress = File.ReadAllText(GetIPAddressPath()); |
|||
if (storedIPAddress != null) |
|||
{ |
|||
Logger.Log($"Found stored IP address {storedIPAddress}"); |
|||
m_IPAddressField.text = storedIPAddress; |
|||
} |
|||
else |
|||
{ |
|||
Logger.Log($"No IP address tored at {GetIPAddressPath()}"); |
|||
} |
|||
} |
|||
|
|||
if (m_JoinButton != null) |
|||
m_JoinButton.gameObject.SetActive(true); |
|||
|
|||
if (m_HostButton != null) |
|||
m_HostButton.gameObject.SetActive(true); |
|||
} |
|||
|
|||
void OnDisable() |
|||
{ |
|||
if (m_JoinButton != null) |
|||
m_JoinButton.gameObject.SetActive(false); |
|||
|
|||
if (m_HostButton != null) |
|||
m_HostButton.gameObject.SetActive(false); |
|||
|
|||
if (m_IPAddressField != null) |
|||
m_IPAddressField.gameObject.SetActive(false); |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: bbcf44277865e45d399575001e42f155 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Net.Sockets; |
|||
using System.Text; |
|||
using System.Threading; |
|||
using UnityEngine; |
|||
using UnityEngine.UI; |
|||
using UnityEngine.XR.ARFoundation; |
|||
|
|||
[RequireComponent(typeof(ClientServerSelector))] |
|||
public class TCPClient : TCPConnection |
|||
{ |
|||
string m_ServerIP; |
|||
|
|||
public string serverIP |
|||
{ |
|||
get { return m_ServerIP; } |
|||
set |
|||
{ |
|||
if (enabled) |
|||
throw new InvalidOperationException("Cannot change server IP address while enabled."); |
|||
|
|||
m_ServerIP = value; |
|||
} |
|||
} |
|||
|
|||
public void Connect() |
|||
{ |
|||
Logger.Log($"Connecting to {serverIP} on port {port}"); |
|||
|
|||
try |
|||
{ |
|||
m_TcpClient = new TcpClient(serverIP, port); |
|||
Logger.Log("Connected!"); |
|||
} |
|||
catch (SocketException e) |
|||
{ |
|||
Logger.Log(e.Message); |
|||
enabled = false; |
|||
GetComponent<ClientServerSelector>().enabled = true; |
|||
} |
|||
} |
|||
|
|||
protected override void OnEnable() |
|||
{ |
|||
base.OnEnable(); |
|||
Connect(); |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: a4bbe7d8f963c43d7b710873f90b9b2d |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Net; |
|||
using System.Net.Sockets; |
|||
using System.Runtime.InteropServices; |
|||
using System.Threading; |
|||
using Unity.Collections; |
|||
using Unity.Collections.LowLevel.Unsafe; |
|||
using UnityEngine; |
|||
using UnityEngine.UI; |
|||
using UnityEngine.XR.ARFoundation; |
|||
using UnityEngine.XR.ARSubsystems; |
|||
#if UNITY_IOS
|
|||
using UnityEngine.XR.ARKit; |
|||
#endif
|
|||
|
|||
public abstract class TCPConnection : MonoBehaviour |
|||
{ |
|||
[SerializeField] |
|||
ARSession m_Session; |
|||
|
|||
public ARSession session |
|||
{ |
|||
get { return m_Session; } |
|||
set { m_Session = value; } |
|||
} |
|||
|
|||
[SerializeField] |
|||
int m_Port = 8502; |
|||
|
|||
public int port |
|||
{ |
|||
get { return m_Port; } |
|||
set { m_Port = value; } |
|||
} |
|||
|
|||
public bool connected |
|||
{ |
|||
get |
|||
{ |
|||
return |
|||
(m_TcpClient != null) && |
|||
(m_TcpClient.Connected); |
|||
} |
|||
} |
|||
|
|||
protected TcpClient m_TcpClient; |
|||
|
|||
protected virtual void OnEnable() |
|||
{ |
|||
#if UNITY_IOS
|
|||
if (ARKitSessionSubsystem.supportsCollaboration) |
|||
{ |
|||
m_ExitRequested = false; |
|||
} |
|||
else |
|||
#endif
|
|||
{ |
|||
Logger.Log("Collaboration is not supported by this device."); |
|||
enabled = false; |
|||
} |
|||
} |
|||
|
|||
protected virtual void OnDisable() |
|||
{ |
|||
#if UNITY_IOS
|
|||
// Shutdown running threads
|
|||
m_ExitRequested = true; |
|||
|
|||
if (m_ReadThread.IsAlive) |
|||
m_ReadThread.Join(); |
|||
|
|||
if (m_SendThread.IsAlive) |
|||
m_SendThread.Join(); |
|||
#endif
|
|||
|
|||
// Close down TCP connection
|
|||
if (m_TcpClient != null) |
|||
{ |
|||
m_TcpClient.Close(); |
|||
Logger.Log("Connection closed"); |
|||
} |
|||
|
|||
m_TcpClient = null; |
|||
} |
|||
|
|||
protected virtual void Update() |
|||
{ |
|||
#if UNITY_IOS
|
|||
if (session == null) |
|||
return; |
|||
|
|||
var subsystem = session.subsystem as ARKitSessionSubsystem; |
|||
if (subsystem == null) |
|||
return; |
|||
|
|||
// Disable collaboration if we aren't connected to anyone
|
|||
subsystem.collaborationEnabled = connected; |
|||
|
|||
if (connected) |
|||
{ |
|||
// Make sure threads are running
|
|||
if (!m_ReadThread.IsAlive) |
|||
m_ReadThread.Start(); |
|||
if (!m_SendThread.IsAlive) |
|||
m_SendThread.Start(); |
|||
|
|||
ProcessRemoteCollaborationData(subsystem); |
|||
CheckForLocalCollaborationData(subsystem); |
|||
} |
|||
#endif
|
|||
} |
|||
|
|||
#if UNITY_IOS
|
|||
Queue<ARCollaborationData> m_CollaborationDataSendQueue; |
|||
|
|||
Queue<ARCollaborationData> m_CollaborationDataReadQueue; |
|||
|
|||
Thread m_ReadThread; |
|||
|
|||
Thread m_SendThread; |
|||
|
|||
bool m_ExitRequested; |
|||
|
|||
void Awake() |
|||
{ |
|||
m_CollaborationDataSendQueue = new Queue<ARCollaborationData>(); |
|||
m_CollaborationDataReadQueue = new Queue<ARCollaborationData>(); |
|||
m_ReadThread = new Thread(ReadThreadProc); |
|||
m_SendThread = new Thread(SendThreadProc); |
|||
} |
|||
|
|||
void SendThreadProc() |
|||
{ |
|||
var stream = m_TcpClient.GetStream(); |
|||
while (!m_ExitRequested) |
|||
{ |
|||
var collaborationData = new ARCollaborationData(); |
|||
int queueSize = 0; |
|||
lock (m_CollaborationDataSendQueue) |
|||
{ |
|||
if (m_CollaborationDataSendQueue.Count > 0) |
|||
{ |
|||
collaborationData = m_CollaborationDataSendQueue.Dequeue(); |
|||
} |
|||
queueSize = m_CollaborationDataSendQueue.Count; |
|||
} |
|||
|
|||
if (collaborationData.valid) |
|||
{ |
|||
// Serialize the collaboration data to a byte array
|
|||
SerializedARCollaborationData serializedData; |
|||
using (collaborationData) |
|||
{ |
|||
// ARCollaborationData can be diposed after being serialized to bytes.
|
|||
serializedData = collaborationData.ToSerialized(); |
|||
} |
|||
|
|||
using (serializedData) |
|||
{ |
|||
// Get the raw data as a NativeSlice
|
|||
var collaborationBytes = serializedData.bytes; |
|||
|
|||
// Construct the message header
|
|||
var header = new MessageHeader |
|||
{ |
|||
messageSize = collaborationBytes.Length, |
|||
messageType = MessageType.CollaborationData |
|||
}; |
|||
|
|||
// Send the header followed by the ARCollaborationData bytes
|
|||
m_WriteBuffer.Send(stream, header); |
|||
m_WriteBuffer.Send(stream, collaborationBytes); |
|||
Logger.Log($"Sent {collaborationBytes.Length} bytes of collaboration data."); |
|||
} |
|||
} |
|||
|
|||
if (queueSize == 0) |
|||
{ |
|||
// If there's nothing else in the queue at the moment,
|
|||
// then go to sleep for a bit.
|
|||
// Otherwise, immediately try to send the next one.
|
|||
Thread.Sleep(1); |
|||
} |
|||
} |
|||
} |
|||
|
|||
unsafe void ReadThreadProc() |
|||
{ |
|||
var stream = m_TcpClient.GetStream(); |
|||
while (!m_ExitRequested) |
|||
{ |
|||
// Loop until there is data available
|
|||
if (!stream.DataAvailable) |
|||
{ |
|||
Thread.Sleep(1); |
|||
continue; |
|||
} |
|||
|
|||
// Read the header
|
|||
var messageHeader = ReadMessageHeader(stream); |
|||
|
|||
// Handle the message
|
|||
switch (messageHeader.messageType) |
|||
{ |
|||
case MessageType.CollaborationData: |
|||
var collaborationData = ReadCollaborationData(stream, messageHeader.messageSize); |
|||
if (collaborationData.valid) |
|||
{ |
|||
// Only store critical data updates; optional updates can come every frame.
|
|||
if (collaborationData.priority == ARCollaborationDataPriority.Critical) |
|||
{ |
|||
lock (m_CollaborationDataReadQueue) |
|||
{ |
|||
m_CollaborationDataReadQueue.Enqueue(collaborationData); |
|||
} |
|||
Logger.Log($"Received {messageHeader.messageSize} bytes of collaboration data."); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
Logger.Log($"Received {messageHeader.messageSize} bytes from remote, but the collaboration data was not valid."); |
|||
} |
|||
break; |
|||
|
|||
default: |
|||
Logger.Log($"Unhandled message type '{messageHeader.messageType}'. Ignoring."); |
|||
|
|||
// We don't understand this message, but read it out anyway
|
|||
// so we can process the next message
|
|||
int bytesRemaining = messageHeader.messageSize; |
|||
while (bytesRemaining > 0) |
|||
{ |
|||
bytesRemaining -= m_ReadBuffer.Read(stream, 0, Mathf.Min(bytesRemaining, m_ReadBuffer.bufferSize)); |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
void CheckForLocalCollaborationData(ARKitSessionSubsystem subsystem) |
|||
{ |
|||
// Exit if no new data is available
|
|||
if (subsystem.collaborationDataCount == 0) |
|||
return; |
|||
|
|||
lock (m_CollaborationDataSendQueue) |
|||
{ |
|||
// Enqueue all new collaboration data with critical priority
|
|||
while (subsystem.collaborationDataCount > 0) |
|||
{ |
|||
var collaborationData = subsystem.DequeueCollaborationData(); |
|||
|
|||
// As all data in this sample is sent over TCP, only send critical data
|
|||
if (collaborationData.priority == ARCollaborationDataPriority.Critical) |
|||
{ |
|||
m_CollaborationDataSendQueue.Enqueue(collaborationData); |
|||
CollaborationNetworkingIndicator.NotifyHasCollaborationData(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
unsafe void ProcessRemoteCollaborationData(ARKitSessionSubsystem subsystem) |
|||
{ |
|||
// Check for remote data and apply it
|
|||
lock (m_CollaborationDataReadQueue) |
|||
{ |
|||
while (m_CollaborationDataReadQueue.Count > 0) |
|||
{ |
|||
using (var collaborationData = m_CollaborationDataReadQueue.Dequeue()) |
|||
{ |
|||
// Assume we only put in valid collaboration data into the queue.
|
|||
subsystem.UpdateWithCollaborationData(collaborationData); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
const int k_BufferSize = 10240; |
|||
|
|||
NetworkBuffer m_ReadBuffer = new NetworkBuffer(k_BufferSize); |
|||
|
|||
NetworkBuffer m_WriteBuffer = new NetworkBuffer(k_BufferSize); |
|||
|
|||
MessageHeader ReadMessageHeader(NetworkStream stream) |
|||
{ |
|||
int bytesRead = m_ReadBuffer.Read(stream, 0, MessageHeader.k_EncodedSize); |
|||
return new MessageHeader(m_ReadBuffer.buffer, bytesRead); |
|||
} |
|||
|
|||
ARCollaborationData ReadCollaborationData(NetworkStream stream, int size) |
|||
{ |
|||
var builder = new ARCollaborationDataBuilder(); |
|||
try |
|||
{ |
|||
int bytesRemaining = size; |
|||
while (bytesRemaining > 0) |
|||
{ |
|||
int bytesRead = m_ReadBuffer.Read(stream, 0, Mathf.Min(bytesRemaining, m_ReadBuffer.bufferSize)); |
|||
builder.Append(m_ReadBuffer.buffer, 0, bytesRead); |
|||
bytesRemaining -= bytesRead; |
|||
} |
|||
|
|||
return builder.ToCollaborationData(); |
|||
} |
|||
finally |
|||
{ |
|||
builder.Dispose(); |
|||
} |
|||
} |
|||
#endif
|
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 5a4eb4ee5b17a4cafa6d0e5cdb157fde |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Net; |
|||
using System.Net.Sockets; |
|||
using System.Text; |
|||
using System.Threading; |
|||
using UnityEngine; |
|||
using Unity.Collections; |
|||
|
|||
public class TCPServer : TCPConnection |
|||
{ |
|||
TcpListener m_TcpListener; |
|||
|
|||
protected override void OnEnable() |
|||
{ |
|||
base.OnEnable(); |
|||
|
|||
m_TcpListener = new TcpListener(IPAddress.Any, port); |
|||
m_TcpListener.Start(); |
|||
Logger.Log($"Listening for connection on port {port}..."); |
|||
} |
|||
|
|||
protected override void Update() |
|||
{ |
|||
if (m_TcpClient == null && m_TcpListener.Pending()) |
|||
{ |
|||
Logger.Log("Connection pending..."); |
|||
m_TcpClient = m_TcpListener.AcceptTcpClient(); |
|||
Logger.Log($"Connection established. {((IPEndPoint)m_TcpClient.Client.RemoteEndPoint).Address}"); |
|||
} |
|||
|
|||
base.Update(); |
|||
} |
|||
|
|||
protected override void OnDisable() |
|||
{ |
|||
base.OnDisable(); |
|||
|
|||
m_TcpListener.Stop(); |
|||
m_TcpListener = null; |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 6fc24ee8011574921a7d764defd91305 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
public interface IMessage |
|||
{ |
|||
int EncodeTo(byte[] bytes); |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 48716d5221a70425aa3409ef5a24a10d |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
public struct MessageHeader : IMessage |
|||
{ |
|||
public int messageSize; |
|||
|
|||
public MessageType messageType; |
|||
|
|||
public int EncodeTo(byte[] bytes) |
|||
{ |
|||
var encoder = new NetworkDataEncoder(bytes); |
|||
encoder.Encode(messageSize); |
|||
encoder.Encode((byte)messageType); |
|||
return encoder.length; |
|||
} |
|||
|
|||
public const int k_EncodedSize = sizeof(int) + sizeof(byte); |
|||
|
|||
public MessageHeader(byte[] bytes, int size) |
|||
{ |
|||
var decoder = new NetworkDataDecoder(bytes, size); |
|||
messageSize = decoder.DecodeInt(); |
|||
messageType = (MessageType)decoder.DecodeByte(); |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 392beb324d6f3400ab0b5ffe1ae43b70 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
public enum MessageType : byte |
|||
{ |
|||
None, |
|||
|
|||
CollaborationData |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: b16d841d0e5dd48d0a70c79beff77499 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Net.Sockets; |
|||
using UnityEngine; |
|||
using Unity.Collections; |
|||
using Unity.Collections.LowLevel.Unsafe; |
|||
|
|||
/// <summary>
|
|||
/// Sends and receives data using a fixed size byte[] buffer. Because the
|
|||
/// buffer is reused, no additional GC allocations are made after construction.
|
|||
/// </summary>
|
|||
public struct NetworkBuffer |
|||
{ |
|||
byte[] m_Buffer; |
|||
|
|||
public NetworkBuffer(int bufferSize) |
|||
{ |
|||
m_Buffer = new byte[bufferSize]; |
|||
} |
|||
|
|||
public byte[] buffer => m_Buffer; |
|||
|
|||
public int bufferSize => (m_Buffer == null) ? 0 : m_Buffer.Length; |
|||
|
|||
public int Read(NetworkStream stream, int offset, int size) |
|||
{ |
|||
ValidateAndThrow(stream); |
|||
|
|||
if (offset < 0) |
|||
throw new ArgumentOutOfRangeException(nameof(offset), offset, $"{nameof(offset)} must be greater than or equal to zero."); |
|||
|
|||
if (size < 0) |
|||
throw new ArgumentOutOfRangeException(nameof(size), size, $"{nameof(size)} must be greater than or equal to zero."); |
|||
|
|||
if (offset + size > m_Buffer.Length) |
|||
throw new InvalidOperationException($"Reading {size} bytes starting at offset {offset} would read past the end of the buffer (buffer length = {m_Buffer.Length})."); |
|||
|
|||
int bytesRemaining = size; |
|||
while (bytesRemaining > 0) |
|||
{ |
|||
int bytesRead = stream.Read(m_Buffer, offset, bytesRemaining); |
|||
CollaborationNetworkingIndicator.NotifyIncomingDataReceived(); |
|||
offset += bytesRead; |
|||
bytesRemaining -= bytesRead; |
|||
} |
|||
|
|||
return size; |
|||
} |
|||
|
|||
public void Send(NetworkStream stream, int offset, int size) |
|||
{ |
|||
ValidateAndThrow(stream); |
|||
|
|||
if (offset + size > m_Buffer.Length) |
|||
throw new InvalidOperationException($"Writing {size} bytes starting at offset {offset} would write past the end of the buffer (buffer length = {m_Buffer.Length})."); |
|||
|
|||
try |
|||
{ |
|||
stream.Write(m_Buffer, offset, size); |
|||
CollaborationNetworkingIndicator.NotifyOutgoingDataSent(); |
|||
} |
|||
catch (SocketException socketException) |
|||
{ |
|||
Logger.Log($"Socket exception: {socketException}"); |
|||
} |
|||
} |
|||
|
|||
public unsafe void Send(NetworkStream stream, NativeSlice<byte> bytes) |
|||
{ |
|||
ValidateAndThrow(stream); |
|||
|
|||
var basePtr = new IntPtr(bytes.GetUnsafeReadOnlyPtr()); |
|||
int bytesRemaining = bytes.Length; |
|||
int offset = 0; |
|||
|
|||
while (bytesRemaining > 0) |
|||
{ |
|||
// Memcpy next chunk into destinationBuffer
|
|||
int size = Mathf.Min(m_Buffer.Length, bytesRemaining); |
|||
fixed(byte* dst = m_Buffer) |
|||
{ |
|||
var src = basePtr + offset; |
|||
UnsafeUtility.MemCpy(dst, (void*)src, size); |
|||
} |
|||
|
|||
bytesRemaining -= size; |
|||
offset += size; |
|||
|
|||
Send(stream, 0, size); |
|||
} |
|||
} |
|||
|
|||
public void Send<T>(NetworkStream stream, T message) where T : struct, IMessage |
|||
{ |
|||
ValidateAndThrow(stream); |
|||
|
|||
int size = message.EncodeTo(m_Buffer); |
|||
Send(stream, 0, size); |
|||
} |
|||
|
|||
void ValidateAndThrow(NetworkStream stream) |
|||
{ |
|||
if (stream == null) |
|||
throw new ArgumentNullException(nameof(stream)); |
|||
|
|||
if (m_Buffer == null) |
|||
throw new InvalidOperationException($"{nameof(NetworkBuffer)} has not been initialized."); |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 6159b84b308ec4356bdc9ae17d26964b |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Net; |
|||
|
|||
public struct NetworkDataDecoder |
|||
{ |
|||
byte[] m_Buffer; |
|||
|
|||
int m_Offset; |
|||
|
|||
int m_Length; |
|||
|
|||
public NetworkDataDecoder(byte[] buffer, int size) |
|||
{ |
|||
if (buffer == null) |
|||
throw new ArgumentNullException(nameof(buffer)); |
|||
|
|||
if (size > buffer.Length) |
|||
throw new ArgumentOutOfRangeException(nameof(size), size, $"'{nameof(size)}' is greater than the length of {nameof(buffer)} ({buffer.Length})."); |
|||
|
|||
m_Buffer = buffer; |
|||
m_Offset = 0; |
|||
m_Length = size; |
|||
} |
|||
|
|||
public unsafe float DecodeFloat() |
|||
{ |
|||
var value = DecodeInt(); |
|||
return *(float*)&value; |
|||
} |
|||
|
|||
public unsafe double DecodeDouble() |
|||
{ |
|||
var value = DecodeLong(); |
|||
return *(double*)&value; |
|||
} |
|||
|
|||
public ushort DecodeUShort() => (ushort)DecodeShort(); |
|||
|
|||
public uint DecodeUInt() => (uint)DecodeInt(); |
|||
|
|||
public ulong DecodeULong() => (ulong)DecodeLong(); |
|||
|
|||
public byte DecodeByte() |
|||
{ |
|||
if (m_Offset >= m_Length) |
|||
throw new InvalidOperationException("Buffer is exhausted. Cannot decode more data."); |
|||
|
|||
return m_Buffer[m_Offset++]; |
|||
} |
|||
|
|||
public unsafe short DecodeShort() |
|||
{ |
|||
if (m_Offset + 2 > m_Length) |
|||
throw new InvalidOperationException("Buffer is exhausted. Cannot decode more data."); |
|||
|
|||
fixed(byte* ptr = &m_Buffer[m_Offset]) |
|||
{ |
|||
m_Offset += 2; |
|||
return IPAddress.NetworkToHostOrder(*(short*)ptr); |
|||
} |
|||
} |
|||
|
|||
public unsafe int DecodeInt() |
|||
{ |
|||
if (m_Offset + 4 > m_Length) |
|||
throw new InvalidOperationException("Buffer is exhausted. Cannot decode more data."); |
|||
|
|||
fixed(byte* ptr = &m_Buffer[m_Offset]) |
|||
{ |
|||
m_Offset += 4; |
|||
return IPAddress.NetworkToHostOrder(*(int*)ptr); |
|||
} |
|||
} |
|||
|
|||
public unsafe long DecodeLong() |
|||
{ |
|||
if (m_Offset + 8 > m_Length) |
|||
throw new InvalidOperationException("Buffer is exhausted. Cannot decode more data."); |
|||
|
|||
fixed(byte* ptr = &m_Buffer[m_Offset]) |
|||
{ |
|||
m_Offset += 8; |
|||
return IPAddress.NetworkToHostOrder(*(long*)ptr); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: d3331be3e25f44589a8a9895cdb60f02 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Net; |
|||
|
|||
public struct NetworkDataEncoder |
|||
{ |
|||
byte[] m_Buffer; |
|||
|
|||
int m_Offset; |
|||
|
|||
public NetworkDataEncoder(byte[] buffer) |
|||
{ |
|||
if (buffer == null) |
|||
throw new ArgumentNullException(nameof(buffer)); |
|||
|
|||
m_Buffer = buffer; |
|||
m_Offset = 0; |
|||
} |
|||
|
|||
public int length => m_Offset; |
|||
|
|||
public unsafe void Encode(float value) => Encode(*(int*)&value); |
|||
|
|||
public unsafe void Encode(double value) => Encode(*(long*)&value); |
|||
|
|||
public void Encode(ushort value) => Encode((short)value); |
|||
|
|||
public void Encode(uint value) => Encode((int)value); |
|||
|
|||
public void Encode(ulong value) => Encode((long)value); |
|||
|
|||
public void Encode(byte value) |
|||
{ |
|||
if (m_Offset + 1 > m_Buffer.Length) |
|||
throw new InvalidOperationException("Buffer is full. Cannot write more data."); |
|||
|
|||
m_Buffer[m_Offset++] = value; |
|||
} |
|||
|
|||
public unsafe void Encode(short value) |
|||
{ |
|||
int newOffset = m_Offset + 2; |
|||
if (newOffset > m_Buffer.Length) |
|||
throw new InvalidOperationException("Buffer is full. Cannot write more data."); |
|||
|
|||
fixed(byte* ptr = &m_Buffer[m_Offset]) |
|||
{ |
|||
*(short*)ptr = IPAddress.HostToNetworkOrder(value); |
|||
} |
|||
m_Offset = newOffset; |
|||
} |
|||
|
|||
public unsafe void Encode(int value) |
|||
{ |
|||
int newOffset = m_Offset + 4; |
|||
if (newOffset > m_Buffer.Length) |
|||
throw new InvalidOperationException("Buffer is full. Cannot write more data."); |
|||
|
|||
fixed(byte* ptr = &m_Buffer[m_Offset]) |
|||
{ |
|||
*(int*)ptr = IPAddress.HostToNetworkOrder(value); |
|||
} |
|||
m_Offset = newOffset; |
|||
} |
|||
|
|||
public unsafe void Encode(long value) |
|||
{ |
|||
int newOffset = m_Offset + 8; |
|||
if (newOffset > m_Buffer.Length) |
|||
throw new InvalidOperationException("Buffer is full. Cannot write more data."); |
|||
|
|||
fixed(byte* ptr = &m_Buffer[m_Offset]) |
|||
{ |
|||
*(long*)ptr = IPAddress.HostToNetworkOrder(value); |
|||
} |
|||
m_Offset = newOffset; |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 03435c1a2d82b46f98fe7980c199a46f |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
撰写
预览
正在加载...
取消
保存
Reference in new issue