com.unity.netcode.gameobjects@1.0.0-pre.2

# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.0.0-pre.2] - 2020-12-20

### Added

- Associated Known Issues for the 1.0.0-pre.1 release in the changelog

### Changed

- Updated label for `1.0.0-pre.1` changelog section

## [1.0.0-pre.1] - 2020-12-20

### Added

- Added `ClientNetworkTransform` sample to the SDK package (#1168)
- Added `Bootstrap` sample to the SDK package (#1140)
- Enhanced `NetworkSceneManager` implementation with additive scene loading capabilities (#1080, #955, #913)
  - `NetworkSceneManager.OnSceneEvent` provides improved scene event notificaitons
- Enhanced `NetworkTransform` implementation with per axis/component based and threshold based state replication (#1042, #1055, #1061, #1084, #1101)
- Added a jitter-resistent `BufferedLinearInterpolator<T>` for `NetworkTransform` (#1060)
- Implemented `NetworkPrefabHandler` that provides support for object pooling and `NetworkPrefab` overrides (#1073, #1004, #977, #905,#749, #727)
- Implemented auto `NetworkObject` transform parent synchronization at runtime over the network (#855)
- Adopted Unity C# Coding Standards in the codebase with `.editorconfig` ruleset (#666, #670)
- When a client tries to spawn a `NetworkObject` an exception is thrown to indicate unsupported behavior. (#981)
- Added a `NetworkTime` and `NetworkTickSystem` which allows for improved control over time and ticks. (#845)
- Added a `OnNetworkDespawn` function to `NetworkObject` which gets called when a `NetworkObject` gets despawned and can be overriden. (#865)
- Added `SnapshotSystem` that would allow variables and spawn/despawn messages to be sent in blocks (#805, #852, #862, #963, #1012, #1013, #1021, #1040, #1062, #1064, #1083, #1091, #1111, #1129, #1166, #1192)
  - Disabled by default for now, except spawn/despawn messages
  - Will leverage unreliable messages with eventual consistency
- `NetworkBehaviour` and `NetworkObject`'s `NetworkManager` instances can now be overriden (#762)
- Added metrics reporting for the new network profiler if the Multiplayer Tools package is present (#1104, #1089, #1096, #1086, #1072, #1058, #960, #897, #891, #878)
- `NetworkBehaviour.IsSpawned` a quick (and stable) way to determine if the associated NetworkObject is spawned (#1190)
- Added `NetworkRigidbody` and `NetworkRigidbody2D` components to support networking `Rigidbody` and `Rigidbody2D` components (#1202, #1175)
- Added `NetworkObjectReference` and `NetworkBehaviourReference` structs which allow to sending `NetworkObject/Behaviours` over RPCs/`NetworkVariable`s (#1173)
- Added `NetworkAnimator` component to support networking `Animator` component (#1281, #872)

### Changed

- Bumped minimum Unity version, renamed package as "Unity Netcode for GameObjects", replaced `MLAPI` namespace and its variants with `Unity.Netcode` namespace and per asm-def variants (#1007, #1009, #1015, #1017, #1019, #1025, #1026, #1065)
  - Minimum Unity version:
    - 2019.4 → 2020.3+
  - Package rename:
    - Display name: `MLAPI Networking Library` → `Netcode for GameObjects`
    - Name: `com.unity.multiplayer.mlapi` → `com.unity.netcode.gameobjects`
    - Updated package description
  - All `MLAPI.x` namespaces are replaced with `Unity.Netcode`
    - `MLAPI.Messaging` → `Unity.Netcode`
    - `MLAPI.Connection` → `Unity.Netcode`
    - `MLAPI.Logging` → `Unity.Netcode`
    - `MLAPI.SceneManagement` → `Unity.Netcode`
    - and other `MLAPI.x` variants to `Unity.Netcode`
  - All assembly definitions are renamed with `Unity.Netcode.x` variants
    - `Unity.Multiplayer.MLAPI.Runtime` → `Unity.Netcode.Runtime`
    - `Unity.Multiplayer.MLAPI.Editor` → `Unity.Netcode.Editor`
    - and other `Unity.Multiplayer.MLAPI.x` variants to `Unity.Netcode.x` variants
- Renamed `Prototyping` namespace and assembly definition to `Components` (#1145)
- Changed `NetworkObject.Despawn(bool destroy)` API to default to `destroy = true` for better usability (#1217)
- Scene registration in `NetworkManager` is now replaced by Build Setttings → Scenes in Build List (#1080)
- `NetworkSceneManager.SwitchScene` has been replaced by `NetworkSceneManager.LoadScene` (#955)
- `NetworkManager, NetworkConfig, and NetworkSceneManager` scene registration replaced with scenes in build list (#1080)
- `GlobalObjectIdHash` replaced `PrefabHash` and `PrefabHashGenerator` for stability and consistency (#698)
- `NetworkStart` has been renamed to `OnNetworkSpawn`. (#865)
- Network variable cleanup - eliminated shared mode, variables are server-authoritative (#1059, #1074)
- `NetworkManager` and other systems are no longer singletons/statics (#696, #705, #706, #737, #738, #739, #746, #747, #763, #765, #766, #783, #784, #785, #786, #787, #788)
- Changed `INetworkSerializable.NetworkSerialize` method signature to use `BufferSerializer<T>` instead of `NetworkSerializer` (#1187)
- Changed `CustomMessagingManager`'s methods to use `FastBufferWriter` and `FastBufferReader` instead of `Stream` (#1187)
- Reduced internal runtime allocations by removing LINQ calls and replacing managed lists/arrays with native collections (#1196)

### Removed

- Removed `NetworkNavMeshAgent` (#1150)
- Removed `NetworkDictionary`, `NetworkSet` (#1149)
- Removed `NetworkVariableSettings` (#1097)
- Removed predefined `NetworkVariable<T>` types (#1093)
    - Removed `NetworkVariableBool`, `NetworkVariableByte`, `NetworkVariableSByte`, `NetworkVariableUShort`, `NetworkVariableShort`, `NetworkVariableUInt`, `NetworkVariableInt`, `NetworkVariableULong`, `NetworkVariableLong`, `NetworkVariableFloat`, `NetworkVariableDouble`, `NetworkVariableVector2`, `NetworkVariableVector3`, `NetworkVariableVector4`, `NetworkVariableColor`, `NetworkVariableColor32`, `NetworkVariableRay`, `NetworkVariableQuaternion`
- Removed `NetworkChannel` and `MultiplexTransportAdapter` (#1133)
- Removed ILPP backend for 2019.4, minimum required version is 2020.3+ (#895)
- `NetworkManager.NetworkConfig` had the following properties removed: (#1080)
  - Scene Registrations no longer exists
  - Allow Runtime Scene Changes was no longer needed and was removed
- Removed the NetworkObject.Spawn payload parameter (#1005)
- Removed `ProfilerCounter`, the original MLAPI network profiler, and the built-in network profiler module (2020.3). A replacement can now be found in the Multiplayer Tools package. (#1048)
- Removed UNet RelayTransport and related relay functionality in UNetTransport (#1081)
- Removed `UpdateStage` parameter from `ServerRpcSendParams` and `ClientRpcSendParams` (#1187)
- Removed `NetworkBuffer`, `NetworkWriter`, `NetworkReader`, `NetworkSerializer`, `PooledNetworkBuffer`, `PooledNetworkWriter`, and `PooledNetworkReader` (#1187)
- Removed `EnableNetworkVariable` in `NetworkConfig`, it is always enabled now (#1179)
- Removed `NetworkTransform`'s FixedSendsPerSecond, AssumeSyncedSends, InterpolateServer, ExtrapolatePosition, MaxSendsToExtrapolate, Channel, EnableNonProvokedResendChecks, DistanceSendrate (#1060) (#826) (#1042, #1055, #1061, #1084, #1101)
- Removed `NetworkManager`'s `StopServer()`, `StopClient()` and `StopHost()` methods and replaced with single `NetworkManager.Shutdown()` method for all (#1108)

### Fixed

- Fixed ServerRpc ownership check to `Debug.LogError` instead of `Debug.LogWarning` (#1126)
- Fixed `NetworkObject.OwnerClientId` property changing before `NetworkBehaviour.OnGainedOwnership()` callback (#1092)
- Fixed `NetworkBehaviourILPP` to iterate over all types in an assembly (#803)
- Fixed cross-asmdef RPC ILPP by importing types into external assemblies (#678)
- Fixed `NetworkManager` shutdown when quitting the application or switching scenes (#1011)
  - Now `NetworkManager` shutdowns correctly and despawns existing `NetworkObject`s
- Fixed Only one `PlayerPrefab` can be selected on `NetworkManager` inspector UI in the editor (#676)
- Fixed connection approval not being triggered for host (#675)
- Fixed various situations where messages could be processed in an invalid order, resulting in errors (#948, #1187, #1218)
- Fixed `NetworkVariable`s being default-initialized on the client instead of being initialized with the desired value (#1266)
- Improved runtime performance and reduced GC pressure (#1187)
- Fixed #915 - clients are receiving data from objects not visible to them (#1099)
- Fixed `NetworkTransform`'s "late join" issues, `NetworkTransform` now uses `NetworkVariable`s instead of RPCs (#826)
- Throw an exception for silent failure when a client tries to get another player's `PlayerObject`, it is now only allowed on the server-side (#844)

### Known Issues

- `NetworkVariable` does not serialize `INetworkSerializable` types through their `NetworkSerialize` implementation
- `NetworkObjects` marked as `DontDestroyOnLoad` are disabled during some network scene transitions
- `NetworkTransform` interpolates from the origin when switching Local Space synchronization
- Exceptions thrown in `OnNetworkSpawn` user code for an object will prevent the callback in other objects
- Cannot send an array of `INetworkSerializable` in RPCs
- ILPP generation fails with special characters in project path

## [0.2.0] - 2021-06-03

WIP version increment to pass package validation checks. Changelog & final version number TBD.

## [0.1.1] - 2021-06-01

This is hotfix v0.1.1 for the initial experimental Unity MLAPI Package.

### Changed

- Fixed issue with the Unity Registry package version missing some fixes from the v0.1.0 release.

## [0.1.0] - 2021-03-23

This is the initial experimental Unity MLAPI Package, v0.1.0.

### Added

- Refactored a new standard for Remote Procedure Call (RPC) in MLAPI which provides increased performance, significantly reduced boilerplate code, and extensibility for future-proofed code. MLAPI RPC includes `ServerRpc` and `ClientRpc` to execute logic on the server and client-side. This provides a single performant unified RPC solution, replacing MLAPI Convenience and Performance RPC (see [here](#removed-features)).
- Added standarized serialization types, including built-in and custom serialization flows. See [RFC #2](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0002-serializable-types.md) for details.
- `INetworkSerializable` interface replaces `IBitWritable`.
- Added `NetworkSerializer`..., which is the main aggregator that implements serialization code for built-in supported types and holds `NetworkReader` and `NetworkWriter` instances internally.
- Added a Network Update Loop infrastructure that aids Netcode systems to update (such as RPC queue and transport) outside of the standard `MonoBehaviour` event cycle. See [RFC #8](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0008-network-update-loop.md) and the following details:
  - It uses Unity's [low-level Player Loop API](https://docs.unity3d.com/ScriptReference/LowLevel.PlayerLoop.html) and allows for registering `INetworkUpdateSystem`s with `NetworkUpdate` methods to be executed at specific `NetworkUpdateStage`s, which may also be before or after `MonoBehaviour`-driven game logic execution.
  - You will typically interact with `NetworkUpdateLoop` for registration and `INetworkUpdateSystem` for implementation.
  - `NetworkVariable`s are now tick-based using the `NetworkTickSystem`, tracking time through network interactions and syncs.
- Added message batching to handle consecutive RPC requests sent to the same client. `RpcBatcher` sends batches based on requests from the `RpcQueueProcessing`, by batch size threshold or immediately.
- [GitHub 494](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/494): Added a constraint to allow one `NetworkObject` per `GameObject`, set through the `DisallowMultipleComponent` attribute.
- Integrated MLAPI with the Unity Profiler for versions 2020.2 and later:
  - Added new profiler modules for MLAPI that report important network data.
  - Attached the profiler to a remote player to view network data over the wire.
- A test project is available for building and experimenting with MLAPI features. This project is available in the MLAPI GitHub [testproject folder](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/tree/release/0.1.0/testproject).
- Added a [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) new GitHub repository to accept extensions from the MLAPI community. Current extensions include moved MLAPI features for lag compensation (useful for Server Authoritative actions) and `TrackedObject`.

### Changed

- [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): MLAPI now uses the Unity Package Manager for installation management.
- Added functionality and usability to `NetworkVariable`, previously called `NetworkVar`. Updates enhance options and fully replace the need for `SyncedVar`s.
- [GitHub 507](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/507): Reimplemented `NetworkAnimator`, which synchronizes animation states for networked objects.
- GitHub [444](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/444) and [455](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/455): Channels are now represented as bytes instead of strings.

For users of previous versions of MLAPI, this release renames APIs due to refactoring. All obsolete marked APIs have been removed as per [GitHub 513](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/513) and [GitHub 514](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/514).

| Previous MLAPI Versions | V 0.1.0 Name |
| -- | -- |
| `NetworkingManager` | `NetworkManager` |
| `NetworkedObject` | `NetworkObject` |
| `NetworkedBehaviour` | `NetworkBehaviour` |
| `NetworkedClient` | `NetworkClient` |
| `NetworkedPrefab` | `NetworkPrefab` |
| `NetworkedVar` | `NetworkVariable` |
| `NetworkedTransform` | `NetworkTransform` |
| `NetworkedAnimator` | `NetworkAnimator` |
| `NetworkedAnimatorEditor` | `NetworkAnimatorEditor` |
| `NetworkedNavMeshAgent` | `NetworkNavMeshAgent` |
| `SpawnManager` | `NetworkSpawnManager` |
| `BitStream` | `NetworkBuffer` |
| `BitReader` | `NetworkReader` |
| `BitWriter` | `NetworkWriter` |
| `NetEventType` | `NetworkEventType` |
| `ChannelType` | `NetworkDelivery` |
| `Channel` | `NetworkChannel` |
| `Transport` | `NetworkTransport` |
| `NetworkedDictionary` | `NetworkDictionary` |
| `NetworkedList` | `NetworkList` |
| `NetworkedSet` | `NetworkSet` |
| `MLAPIConstants` | `NetworkConstants` |
| `UnetTransport` | `UNetTransport` |

### Fixed

- [GitHub 460](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/460): Fixed an issue for RPC where the host-server was not receiving RPCs from the host-client and vice versa without the loopback flag set in `NetworkingManager`.
- Fixed an issue where data in the Profiler was incorrectly aggregated and drawn, which caused the profiler data to increment indefinitely instead of resetting each frame.
- Fixed an issue the client soft-synced causing PlayMode client-only scene transition issues, caused when running the client in the editor and the host as a release build. Users may have encountered a soft sync of `NetworkedInstanceId` issues in the `SpawnManager.ClientCollectSoftSyncSceneObjectSweep` method.
- [GitHub 458](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/458): Fixed serialization issues in `NetworkList` and `NetworkDictionary` when running in Server mode.
- [GitHub 498](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/498): Fixed numerical precision issues to prevent not a number (NaN) quaternions.
- [GitHub 438](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/438): Fixed booleans by reaching or writing bytes instead of bits.
- [GitHub 519](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/519): Fixed an issue where calling `Shutdown()` before making `NetworkManager.Singleton = null` is null on `NetworkManager.OnDestroy()`.

### Removed

With a new release of MLAPI in Unity, some features have been removed:

- SyncVars have been removed from MLAPI. Use `NetworkVariable`s in place of this functionality. <!-- MTT54 -->
- [GitHub 527](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/527): Lag compensation systems and `TrackedObject` have moved to the new [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) repo.
- [GitHub 509](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/509): Encryption has been removed from MLAPI. The `Encryption` option in `NetworkConfig` on the `NetworkingManager` is not available in this release. This change will not block game creation or running. A current replacement for this functionality is not available, and may be developed in future releases. See the following changes:
    - Removed `SecuritySendFlags` from all APIs.
    - Removed encryption, cryptography, and certificate configurations from APIs including `NetworkManager` and `NetworkConfig`.
    - Removed "hail handshake", including `NetworkManager` implementation and `NetworkConstants` entries.
    - Modified `RpcQueue` and `RpcBatcher` internals to remove encryption and authentication from reading and writing.
- Removed the previous MLAPI Profiler editor window from Unity versions 2020.2 and later.
- Removed previous MLAPI Convenience and Performance RPC APIs with the new standard RPC API. See [RFC #1](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0001-std-rpc-api.md) for details.
- [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): Removed the MLAPI Installer.

### Known Issues

- `NetworkNavMeshAgent` does not synchronize mesh data, Agent Size, Steering, Obstacle Avoidance, or Path Finding settings. It only synchronizes the destination and velocity, not the path to the destination.
- For `RPC`, methods with a `ClientRpc` or `ServerRpc` suffix which are not marked with [ServerRpc] or [ClientRpc] will cause a compiler error.
- For `NetworkAnimator`, Animator Overrides are not supported. Triggers do not work.
- For `NetworkVariable`, the `NetworkDictionary` `List` and `Set` must use the `reliableSequenced` channel.
- `NetworkObjects`s are supported but when spawning a prefab with nested child network objects you have to manually call spawn on them
- `NetworkTransform` have the following issues:
  - Replicated objects may have jitter.
  - The owner is always authoritative about the object's position.
  - Scale is not synchronized.
- Connection Approval is not called on the host client.
- For `NamedMessages`, always use `NetworkBuffer` as the underlying stream for sending named and unnamed messages.
- For `NetworkManager`, connection management is limited. Use `IsServer`, `IsClient`, `IsConnectedClient`, or other code to check if MLAPI connected correctly.

## [0.0.1-preview.1] - 2020-12-20

This was an internally-only-used version of the Unity MLAPI Package
This commit is contained in:
Unity Technologies
2020-12-20 00:00:00 +00:00
commit 22d877d1b2
489 changed files with 43246 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
namespace Unity.Netcode
{
/// <summary>
/// Arithmetic helper class
/// </summary>
public static class Arithmetic
{
// Sign bits for different data types
internal const long SIGN_BIT_64 = -9223372036854775808;
internal const int SIGN_BIT_32 = -2147483648;
internal const short SIGN_BIT_16 = -32768;
internal const sbyte SIGN_BIT_8 = -128;
// Ceiling function that doesn't deal with floating point values
// these only work correctly with possitive numbers
internal static ulong CeilingExact(ulong u1, ulong u2) => (u1 + u2 - 1) / u2;
internal static long CeilingExact(long u1, long u2) => (u1 + u2 - 1) / u2;
internal static uint CeilingExact(uint u1, uint u2) => (u1 + u2 - 1) / u2;
internal static int CeilingExact(int u1, int u2) => (u1 + u2 - 1) / u2;
internal static ushort CeilingExact(ushort u1, ushort u2) => (ushort)((u1 + u2 - 1) / u2);
internal static short CeilingExact(short u1, short u2) => (short)((u1 + u2 - 1) / u2);
internal static byte CeilingExact(byte u1, byte u2) => (byte)((u1 + u2 - 1) / u2);
internal static sbyte CeilingExact(sbyte u1, sbyte u2) => (sbyte)((u1 + u2 - 1) / u2);
/// <summary>
/// ZigZag encodes a signed integer and maps it to a unsigned integer
/// </summary>
/// <param name="value">The signed integer to encode</param>
/// <returns>A ZigZag encoded version of the integer</returns>
public static ulong ZigZagEncode(long value) => (ulong)((value >> 63) ^ (value << 1));
/// <summary>
/// Decides a ZigZag encoded integer back to a signed integer
/// </summary>
/// <param name="value">The unsigned integer</param>
/// <returns>The signed version of the integer</returns>
public static long ZigZagDecode(ulong value) => (((long)(value >> 1) & 0x7FFFFFFFFFFFFFFFL) ^ ((long)(value << 63) >> 63));
/// <summary>
/// Gets the output size in bytes after VarInting a unsigned integer
/// </summary>
/// <param name="value">The unsigned integer whose length to get</param>
/// <returns>The amount of bytes</returns>
public static int VarIntSize(ulong value) =>
value <= 240 ? 1 :
value <= 2287 ? 2 :
value <= 67823 ? 3 :
value <= 16777215 ? 4 :
value <= 4294967295 ? 5 :
value <= 1099511627775 ? 6 :
value <= 281474976710655 ? 7 :
value <= 72057594037927935 ? 8 :
9;
internal static long Div8Ceil(ulong value) => (long)((value >> 3) + ((value & 1UL) | ((value >> 1) & 1UL) | ((value >> 2) & 1UL)));
}
}

View File

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

View File

@@ -0,0 +1,126 @@
using System.Runtime.CompilerServices;
namespace Unity.Netcode
{
public static class BitCounter
{
// Since we don't have access to BitOperations.LeadingZeroCount() (which would have been the fastest)
// we use the De Bruijn sequence to do this calculation
// See https://en.wikipedia.org/wiki/De_Bruijn_sequence and https://www.chessprogramming.org/De_Bruijn_Sequence
private const ulong k_DeBruijnMagic64 = 0x37E84A99DAE458F;
private const uint k_DeBruijnMagic32 = 0x06EB14F9;
// We're counting bytes, not bits, so these have all had the operation x/8 + 1 applied
private static readonly int[] k_DeBruijnTableBytes64 =
{
0/8+1, 1/8+1, 17/8+1, 2/8+1, 18/8+1, 50/8+1, 3/8+1, 57/8+1,
47/8+1, 19/8+1, 22/8+1, 51/8+1, 29/8+1, 4/8+1, 33/8+1, 58/8+1,
15/8+1, 48/8+1, 20/8+1, 27/8+1, 25/8+1, 23/8+1, 52/8+1, 41/8+1,
54/8+1, 30/8+1, 38/8+1, 5/8+1, 43/8+1, 34/8+1, 59/8+1, 8/8+1,
63/8+1, 16/8+1, 49/8+1, 56/8+1, 46/8+1, 21/8+1, 28/8+1, 32/8+1,
14/8+1, 26/8+1, 24/8+1, 40/8+1, 53/8+1, 37/8+1, 42/8+1, 7/8+1,
62/8+1, 55/8+1, 45/8+1, 31/8+1, 13/8+1, 39/8+1, 36/8+1, 6/8+1,
61/8+1, 44/8+1, 12/8+1, 35/8+1, 60/8+1, 11/8+1, 10/8+1, 9/8+1,
};
private static readonly int[] k_DeBruijnTableBytes32 =
{
0/8+1, 1/8+1, 16/8+1, 2/8+1, 29/8+1, 17/8+1, 3/8+1, 22/8+1,
30/8+1, 20/8+1, 18/8+1, 11/8+1, 13/8+1, 4/8+1, 7/8+1, 23/8+1,
31/8+1, 15/8+1, 28/8+1, 21/8+1, 19/8+1, 10/8+1, 12/8+1, 6/8+1,
14/8+1, 27/8+1, 9/8+1, 5/8+1, 26/8+1, 8/8+1, 25/8+1, 24/8+1,
};
// And here we're counting the number of set bits, not the position of the highest set,
// so these still have +1 applied - unfortunately 0 and 1 both return the same value.
private static readonly int[] k_DeBruijnTableBits64 =
{
0+1, 1+1, 17+1, 2+1, 18+1, 50+1, 3+1, 57+1,
47+1, 19+1, 22+1, 51+1, 29+1, 4+1, 33+1, 58+1,
15+1, 48+1, 20+1, 27+1, 25+1, 23+1, 52+1, 41+1,
54+1, 30+1, 38+1, 5+1, 43+1, 34+1, 59+1, 8+1,
63+1, 16+1, 49+1, 56+1, 46+1, 21+1, 28+1, 32+1,
14+1, 26+1, 24+1, 40+1, 53+1, 37+1, 42+1, 7+1,
62+1, 55+1, 45+1, 31+1, 13+1, 39+1, 36+1, 6+1,
61+1, 44+1, 12+1, 35+1, 60+1, 11+1, 10+1, 9+1,
};
private static readonly int[] k_DeBruijnTableBits32 =
{
0+1, 1+1, 16+1, 2+1, 29+1, 17+1, 3+1, 22+1,
30+1, 20+1, 18+1, 11+1, 13+1, 4+1, 7+1, 23+1,
31+1, 15+1, 28+1, 21+1, 19+1, 10+1, 12+1, 6+1,
14+1, 27+1, 9+1, 5+1, 26+1, 8+1, 25+1, 24+1,
};
/// <summary>
/// Get the minimum number of bytes required to represent the given value
/// </summary>
/// <param name="value">The value</param>
/// <returns>The number of bytes required</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetUsedByteCount(uint value)
{
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
value = value & ~(value >> 1);
return k_DeBruijnTableBytes32[value * k_DeBruijnMagic32 >> 27];
}
/// <summary>
/// Get the minimum number of bytes required to represent the given value
/// </summary>
/// <param name="value">The value</param>
/// <returns>The number of bytes required</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetUsedByteCount(ulong value)
{
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
value |= value >> 32;
value = value & ~(value >> 1);
return k_DeBruijnTableBytes64[value * k_DeBruijnMagic64 >> 58];
}
/// <summary>
/// Get the minimum number of bits required to represent the given value
/// </summary>
/// <param name="value">The value</param>
/// <returns>The number of bits required</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetUsedBitCount(uint value)
{
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
value = value & ~(value >> 1);
return k_DeBruijnTableBits32[value * k_DeBruijnMagic32 >> 27];
}
/// <summary>
/// Get the minimum number of bits required to represent the given value
/// </summary>
/// <param name="value">The value</param>
/// <returns>The number of bits required</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetUsedBitCount(ulong value)
{
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
value |= value >> 32;
value = value & ~(value >> 1);
return k_DeBruijnTableBits64[value * k_DeBruijnMagic64 >> 58];
}
}
}

View File

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

View File

@@ -0,0 +1,218 @@
using System;
using System.Runtime.CompilerServices;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Netcode
{
/// <summary>
/// Helper class for doing bitwise reads for a FastBufferReader.
/// Ensures all bitwise reads end on proper byte alignment so FastBufferReader doesn't have to be concerned
/// with misaligned reads.
/// </summary>
public ref struct BitReader
{
private FastBufferReader m_Reader;
private readonly unsafe byte* m_BufferPointer;
private readonly int m_Position;
private int m_BitPosition;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
private int m_AllowedBitwiseReadMark;
#endif
private const int k_BitsPerByte = 8;
/// <summary>
/// Whether or not the current BitPosition is evenly divisible by 8. I.e. whether or not the BitPosition is at a byte boundary.
/// </summary>
public bool BitAligned
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (m_BitPosition & 7) == 0;
}
internal unsafe BitReader(FastBufferReader reader)
{
m_Reader = reader;
m_BufferPointer = m_Reader.Handle->BufferPointer + m_Reader.Handle->Position;
m_Position = m_Reader.Handle->Position;
m_BitPosition = 0;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
m_AllowedBitwiseReadMark = (m_Reader.Handle->AllowedReadMark - m_Position) * k_BitsPerByte;
#endif
}
/// <summary>
/// Pads the read bit count to byte alignment and commits the read back to the reader
/// </summary>
public void Dispose()
{
var bytesWritten = m_BitPosition >> 3;
if (!BitAligned)
{
// Accounting for the partial read
++bytesWritten;
}
m_Reader.CommitBitwiseReads(bytesWritten);
}
/// <summary>
/// Verifies the requested bit count can be read from the buffer.
/// This exists as a separate method to allow multiple bit reads to be bounds checked with a single call.
/// If it returns false, you may not read, and in editor and development builds, attempting to do so will
/// throw an exception. In release builds, attempting to do so will read junk memory.
/// </summary>
/// <param name="bitCount">Number of bits you want to read, in total</param>
/// <returns>True if you can read, false if that would exceed buffer bounds</returns>
public unsafe bool TryBeginReadBits(uint bitCount)
{
var newBitPosition = m_BitPosition + bitCount;
var totalBytesWrittenInBitwiseContext = newBitPosition >> 3;
if ((newBitPosition & 7) != 0)
{
// Accounting for the partial read
++totalBytesWrittenInBitwiseContext;
}
if (m_Reader.Handle->Position + totalBytesWrittenInBitwiseContext > m_Reader.Handle->Length)
{
return false;
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
m_AllowedBitwiseReadMark = (int)newBitPosition;
#endif
return true;
}
/// <summary>
/// Read a certain amount of bits from the stream.
/// </summary>
/// <param name="value">Value to store bits into.</param>
/// <param name="bitCount">Amount of bits to read</param>
public unsafe void ReadBits(out ulong value, uint bitCount)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (bitCount > 64)
{
throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 64 bits from a 64-bit value!");
}
if (bitCount < 0)
{
throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read fewer than 0 bits!");
}
int checkPos = (int)(m_BitPosition + bitCount);
if (checkPos > m_AllowedBitwiseReadMark)
{
throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginReadBits)}()");
}
#endif
ulong val = 0;
int wholeBytes = (int)bitCount / k_BitsPerByte;
byte* asBytes = (byte*)&val;
if (BitAligned)
{
if (wholeBytes != 0)
{
ReadPartialValue(out val, wholeBytes);
}
}
else
{
for (var i = 0; i < wholeBytes; ++i)
{
ReadMisaligned(out asBytes[i]);
}
}
val |= (ulong)ReadByteBits((int)bitCount & 7) << ((int)bitCount & ~7);
value = val;
}
/// <summary>
/// Read bits from stream.
/// </summary>
/// <param name="value">Value to store bits into.</param>
/// <param name="bitCount">Amount of bits to read.</param>
public void ReadBits(out byte value, uint bitCount)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
int checkPos = (int)(m_BitPosition + bitCount);
if (checkPos > m_AllowedBitwiseReadMark)
{
throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginReadBits)}()");
}
#endif
value = ReadByteBits((int)bitCount);
}
/// <summary>
/// Read a single bit from the buffer
/// </summary>
/// <param name="bit">Out value of the bit. True represents 1, False represents 0</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ReadBit(out bool bit)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
int checkPos = (m_BitPosition + 1);
if (checkPos > m_AllowedBitwiseReadMark)
{
throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginReadBits)}()");
}
#endif
int offset = m_BitPosition & 7;
int pos = m_BitPosition >> 3;
bit = (m_BufferPointer[pos] & (1 << offset)) != 0;
++m_BitPosition;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void ReadPartialValue<T>(out T value, int bytesToRead, int offsetBytes = 0) where T : unmanaged
{
var val = new T();
byte* ptr = ((byte*)&val) + offsetBytes;
byte* bufferPointer = m_BufferPointer + m_Position;
UnsafeUtility.MemCpy(ptr, bufferPointer, bytesToRead);
m_BitPosition += bytesToRead * k_BitsPerByte;
value = val;
}
private byte ReadByteBits(int bitCount)
{
if (bitCount > 8)
{
throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 8 bits into an 8-bit value!");
}
if (bitCount < 0)
{
throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read fewer than 0 bits!");
}
int result = 0;
var convert = new ByteBool();
for (int i = 0; i < bitCount; ++i)
{
ReadBit(out bool bit);
result |= convert.Collapse(bit) << i;
}
return (byte)result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void ReadMisaligned(out byte value)
{
int off = m_BitPosition & 7;
int pos = m_BitPosition >> 3;
int shift1 = 8 - off;
value = (byte)((m_BufferPointer[pos] >> off) | (m_BufferPointer[(m_BitPosition += 8) >> 3] << shift1));
}
}
}

View File

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

View File

@@ -0,0 +1,211 @@
using System;
using System.Runtime.CompilerServices;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Netcode
{
/// <summary>
/// Helper class for doing bitwise writes for a FastBufferWriter.
/// Ensures all bitwise writes end on proper byte alignment so FastBufferWriter doesn't have to be concerned
/// with misaligned writes.
/// </summary>
public ref struct BitWriter
{
private FastBufferWriter m_Writer;
private unsafe byte* m_BufferPointer;
private readonly int m_Position;
private int m_BitPosition;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
private int m_AllowedBitwiseWriteMark;
#endif
private const int k_BitsPerByte = 8;
/// <summary>
/// Whether or not the current BitPosition is evenly divisible by 8. I.e. whether or not the BitPosition is at a byte boundary.
/// </summary>
public bool BitAligned
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (m_BitPosition & 7) == 0;
}
internal unsafe BitWriter(FastBufferWriter writer)
{
m_Writer = writer;
m_BufferPointer = writer.Handle->BufferPointer + writer.Handle->Position;
m_Position = writer.Handle->Position;
m_BitPosition = 0;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
m_AllowedBitwiseWriteMark = (m_Writer.Handle->AllowedWriteMark - m_Writer.Handle->Position) * k_BitsPerByte;
#endif
}
/// <summary>
/// Pads the written bit count to byte alignment and commits the write back to the writer
/// </summary>
public void Dispose()
{
var bytesWritten = m_BitPosition >> 3;
if (!BitAligned)
{
// Accounting for the partial write
++bytesWritten;
}
m_Writer.CommitBitwiseWrites(bytesWritten);
}
/// <summary>
/// Allows faster serialization by batching bounds checking.
/// When you know you will be writing multiple fields back-to-back and you know the total size,
/// you can call TryBeginWriteBits() once on the total size, and then follow it with calls to
/// WriteBit() or WriteBits().
///
/// Bitwise write operations will throw OverflowException in editor and development builds if you
/// go past the point you've marked using TryBeginWriteBits(). In release builds, OverflowException will not be thrown
/// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following
/// operations in release builds. Instead, attempting to write past the marked position in release builds
/// will write to random memory and cause undefined behavior, likely including instability and crashes.
/// </summary>
/// <param name="bitCount">Number of bits you want to write, in total</param>
/// <returns>True if you can write, false if that would exceed buffer bounds</returns>
public unsafe bool TryBeginWriteBits(int bitCount)
{
var newBitPosition = m_BitPosition + bitCount;
var totalBytesWrittenInBitwiseContext = newBitPosition >> 3;
if ((newBitPosition & 7) != 0)
{
// Accounting for the partial write
++totalBytesWrittenInBitwiseContext;
}
if (m_Position + totalBytesWrittenInBitwiseContext > m_Writer.Handle->Capacity)
{
if (m_Position + totalBytesWrittenInBitwiseContext > m_Writer.Handle->MaxCapacity)
{
return false;
}
if (m_Writer.Handle->Capacity < m_Writer.Handle->MaxCapacity)
{
m_Writer.Grow(totalBytesWrittenInBitwiseContext);
m_BufferPointer = m_Writer.Handle->BufferPointer + m_Writer.Handle->Position;
}
else
{
return false;
}
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
m_AllowedBitwiseWriteMark = newBitPosition;
#endif
return true;
}
/// <summary>
/// Write s certain amount of bits to the stream.
/// </summary>
/// <param name="value">Value to get bits from.</param>
/// <param name="bitCount">Amount of bits to write</param>
public unsafe void WriteBits(ulong value, uint bitCount)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (bitCount > 64)
{
throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot write more than 64 bits from a 64-bit value!");
}
int checkPos = (int)(m_BitPosition + bitCount);
if (checkPos > m_AllowedBitwiseWriteMark)
{
throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWriteBits)}()");
}
#endif
int wholeBytes = (int)bitCount / k_BitsPerByte;
byte* asBytes = (byte*)&value;
if (BitAligned)
{
if (wholeBytes != 0)
{
WritePartialValue(value, wholeBytes);
}
}
else
{
for (var i = 0; i < wholeBytes; ++i)
{
WriteMisaligned(asBytes[i]);
}
}
for (var count = wholeBytes * k_BitsPerByte; count < bitCount; ++count)
{
WriteBit((value & (1UL << count)) != 0);
}
}
/// <summary>
/// Write bits to stream.
/// </summary>
/// <param name="value">Value to get bits from.</param>
/// <param name="bitCount">Amount of bits to write.</param>
public void WriteBits(byte value, uint bitCount)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
int checkPos = (int)(m_BitPosition + bitCount);
if (checkPos > m_AllowedBitwiseWriteMark)
{
throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWriteBits)}()");
}
#endif
for (int i = 0; i < bitCount; ++i)
{
WriteBit(((value >> i) & 1) != 0);
}
}
/// <summary>
/// Write a single bit to the buffer
/// </summary>
/// <param name="bit">Value of the bit. True represents 1, False represents 0</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void WriteBit(bool bit)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
int checkPos = (m_BitPosition + 1);
if (checkPos > m_AllowedBitwiseWriteMark)
{
throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWriteBits)}()");
}
#endif
int offset = m_BitPosition & 7;
int pos = m_BitPosition >> 3;
++m_BitPosition;
m_BufferPointer[pos] = (byte)(bit ? (m_BufferPointer[pos] & ~(1 << offset)) | (1 << offset) : (m_BufferPointer[pos] & ~(1 << offset)));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void WritePartialValue<T>(T value, int bytesToWrite, int offsetBytes = 0) where T : unmanaged
{
byte* ptr = ((byte*)&value) + offsetBytes;
byte* bufferPointer = m_BufferPointer + m_Position;
UnsafeUtility.MemCpy(bufferPointer, ptr, bytesToWrite);
m_BitPosition += bytesToWrite * k_BitsPerByte;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void WriteMisaligned(byte value)
{
int off = m_BitPosition & 7;
int pos = m_BitPosition >> 3;
int shift1 = 8 - off;
m_BufferPointer[pos + 1] = (byte)((m_BufferPointer[pos + 1] & (0xFF << off)) | (value >> shift1));
m_BufferPointer[pos] = (byte)((m_BufferPointer[pos] & (0xFF >> shift1)) | (value << off));
m_BitPosition += 8;
}
}
}

View File

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

View File

@@ -0,0 +1,225 @@
namespace Unity.Netcode
{
/// <summary>
/// Two-way serializer wrapping FastBufferReader or FastBufferWriter.
///
/// Implemented as a ref struct for two reasons:
/// 1. The BufferSerializer cannot outlive the FBR/FBW it wraps or using it will cause a crash
/// 2. The BufferSerializer must always be passed by reference and can't be copied
///
/// Ref structs help enforce both of those rules: they can't out live the stack context in which they were
/// created, and they're always passed by reference no matter what.
///
/// BufferSerializer doesn't wrapp FastBufferReader or FastBufferWriter directly because it can't.
/// ref structs can't implement interfaces, and in order to be able to have two different implementations with
/// the same interface (which allows us to avoid an "if(IsReader)" on every call), the thing directly wrapping
/// the struct has to implement an interface. So IReaderWriter exists as the interface,
/// which is implemented by a normal struct, while the ref struct wraps the normal one to enforce the two above
/// requirements. (Allowing direct access to the IReaderWriter struct would allow dangerous
/// things to happen because the struct's lifetime could outlive the Reader/Writer's.)
/// </summary>
/// <typeparam name="TReaderWriter">The implementation struct</typeparam>
public ref struct BufferSerializer<TReaderWriter> where TReaderWriter : IReaderWriter
{
private TReaderWriter m_Implementation;
/// <summary>
/// Check if the contained implementation is a reader
/// </summary>
public bool IsReader => m_Implementation.IsReader;
/// <summary>
/// Check if the contained implementation is a writer
/// </summary>
public bool IsWriter => m_Implementation.IsWriter;
internal BufferSerializer(TReaderWriter implementation)
{
m_Implementation = implementation;
}
/// <summary>
/// Retrieves the FastBufferReader instance. Only valid if IsReader = true, throws
/// InvalidOperationException otherwise.
/// </summary>
/// <returns>Reader instance</returns>
public FastBufferReader GetFastBufferReader()
{
return m_Implementation.GetFastBufferReader();
}
/// <summary>
/// Retrieves the FastBufferWriter instance. Only valid if IsWriter = true, throws
/// InvalidOperationException otherwise.
/// </summary>
/// <returns>Writer instance</returns>
public FastBufferWriter GetFastBufferWriter()
{
return m_Implementation.GetFastBufferWriter();
}
/// <summary>
/// Serialize an INetworkSerializable
///
/// Throws OverflowException if the end of the buffer has been reached.
/// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
/// </summary>
/// <param name="value">Value to serialize</param>
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new()
{
m_Implementation.SerializeNetworkSerializable(ref value);
}
/// <summary>
/// Serialize a string.
///
/// Note: Will ALWAYS allocate a new string when reading.
///
/// Throws OverflowException if the end of the buffer has been reached.
/// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
/// </summary>
/// <param name="s">Value to serialize</param>
/// <param name="oneByteChars">
/// If true, will truncate each char to one byte.
/// This is slower than two-byte chars, but uses less bandwidth.
/// </param>
public void SerializeValue(ref string s, bool oneByteChars = false)
{
m_Implementation.SerializeValue(ref s, oneByteChars);
}
/// <summary>
/// Serialize an array value.
///
/// Note: Will ALWAYS allocate a new array when reading.
/// If you have a statically-sized array that you know is large enough, it's recommended to
/// serialize the size yourself and iterate serializing array members.
///
/// (This is because C# doesn't allow setting an array's length value, so deserializing
/// into an existing array of larger size would result in an array that doesn't have as many values
/// as its Length indicates it should.)
///
/// Throws OverflowException if the end of the buffer has been reached.
/// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
/// </summary>
/// <param name="array">Value to serialize</param>
public void SerializeValue<T>(ref T[] array) where T : unmanaged
{
m_Implementation.SerializeValue(ref array);
}
/// <summary>
/// Serialize a single byte
///
/// Throws OverflowException if the end of the buffer has been reached.
/// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
/// </summary>
/// <param name="value">Value to serialize</param>
public void SerializeValue(ref byte value)
{
m_Implementation.SerializeValue(ref value);
}
/// <summary>
/// Serialize an unmanaged type. Supports basic value types as well as structs.
/// The provided type will be copied to/from the buffer as it exists in memory.
///
/// Throws OverflowException if the end of the buffer has been reached.
/// Write buffers will grow up to the maximum allowable message size before throwing OverflowException.
/// </summary>
/// <param name="value">Value to serialize</param>
public void SerializeValue<T>(ref T value) where T : unmanaged
{
m_Implementation.SerializeValue(ref value);
}
/// <summary>
/// Allows faster serialization by batching bounds checking.
/// When you know you will be writing multiple fields back-to-back and you know the total size,
/// you can call PreCheck() once on the total size, and then follow it with calls to
/// SerializeValuePreChecked() for faster serialization. Write buffers will grow during PreCheck()
/// if needed.
///
/// PreChecked serialization operations will throw OverflowException in editor and development builds if you
/// go past the point you've marked using PreCheck(). In release builds, OverflowException will not be thrown
/// for performance reasons, since the point of using PreCheck is to avoid bounds checking in the following
/// operations in release builds.
///
/// To get the correct size to check for, use FastBufferWriter.GetWriteSize(value) or
/// FastBufferWriter.GetWriteSize&lt;type&gt;()
/// </summary>
/// <param name="amount">Number of bytes you plan to read or write</param>
/// <returns>True if the read/write can proceed, false otherwise.</returns>
public bool PreCheck(int amount)
{
return m_Implementation.PreCheck(amount);
}
/// <summary>
/// Serialize a string.
///
/// Note: Will ALWAYS allocate a new string when reading.
///
/// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
/// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
/// serialization operations in one function call instead of having to do bounds checking on every call.
/// </summary>
/// <param name="s">Value to serialize</param>
/// <param name="oneByteChars">
/// If true, will truncate each char to one byte.
/// This is slower than two-byte chars, but uses less bandwidth.
/// </param>
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false)
{
m_Implementation.SerializeValuePreChecked(ref s, oneByteChars);
}
/// <summary>
/// Serialize an array value.
///
/// Note: Will ALWAYS allocate a new array when reading.
/// If you have a statically-sized array that you know is large enough, it's recommended to
/// serialize the size yourself and iterate serializing array members.
///
/// (This is because C# doesn't allow setting an array's length value, so deserializing
/// into an existing array of larger size would result in an array that doesn't have as many values
/// as its Length indicates it should.)
///
/// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
/// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
/// serialization operations in one function call instead of having to do bounds checking on every call.
/// </summary>
/// <param name="array">Value to serialize</param>
public void SerializeValuePreChecked<T>(ref T[] array) where T : unmanaged
{
m_Implementation.SerializeValuePreChecked(ref array);
}
/// <summary>
/// Serialize a single byte
///
/// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
/// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
/// serialization operations in one function call instead of having to do bounds checking on every call.
/// </summary>
/// <param name="value">Value to serialize</param>
public void SerializeValuePreChecked(ref byte value)
{
m_Implementation.SerializeValuePreChecked(ref value);
}
/// <summary>
/// Serialize an unmanaged type. Supports basic value types as well as structs.
/// The provided type will be copied to/from the buffer as it exists in memory.
///
/// Using the PreChecked versions of these functions requires calling PreCheck() ahead of time, and they should only
/// be called if PreCheck() returns true. This is an efficiency option, as it allows you to PreCheck() multiple
/// serialization operations in one function call instead of having to do bounds checking on every call.
/// </summary>
/// <param name="value">Value to serialize</param>
public void SerializeValuePreChecked<T>(ref T value) where T : unmanaged
{
m_Implementation.SerializeValuePreChecked(ref value);
}
}
}

View File

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

View File

@@ -0,0 +1,77 @@
using System;
namespace Unity.Netcode
{
internal struct BufferSerializerReader : IReaderWriter
{
private FastBufferReader m_Reader;
public BufferSerializerReader(FastBufferReader reader)
{
m_Reader = reader;
}
public bool IsReader => true;
public bool IsWriter => false;
public FastBufferReader GetFastBufferReader()
{
return m_Reader;
}
public FastBufferWriter GetFastBufferWriter()
{
throw new InvalidOperationException("Cannot retrieve a FastBufferWriter from a serializer where IsWriter = false");
}
public void SerializeValue(ref string s, bool oneByteChars = false)
{
m_Reader.ReadValueSafe(out s, oneByteChars);
}
public void SerializeValue<T>(ref T[] array) where T : unmanaged
{
m_Reader.ReadValueSafe(out array);
}
public void SerializeValue(ref byte value)
{
m_Reader.ReadByteSafe(out value);
}
public void SerializeValue<T>(ref T value) where T : unmanaged
{
m_Reader.ReadValueSafe(out value);
}
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new()
{
m_Reader.ReadNetworkSerializable(out value);
}
public bool PreCheck(int amount)
{
return m_Reader.TryBeginRead(amount);
}
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false)
{
m_Reader.ReadValue(out s, oneByteChars);
}
public void SerializeValuePreChecked<T>(ref T[] array) where T : unmanaged
{
m_Reader.ReadValue(out array);
}
public void SerializeValuePreChecked(ref byte value)
{
m_Reader.ReadValue(out value);
}
public void SerializeValuePreChecked<T>(ref T value) where T : unmanaged
{
m_Reader.ReadValue(out value);
}
}
}

View File

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

View File

@@ -0,0 +1,77 @@
using System;
namespace Unity.Netcode
{
internal struct BufferSerializerWriter : IReaderWriter
{
private FastBufferWriter m_Writer;
public BufferSerializerWriter(FastBufferWriter writer)
{
m_Writer = writer;
}
public bool IsReader => false;
public bool IsWriter => true;
public FastBufferReader GetFastBufferReader()
{
throw new InvalidOperationException("Cannot retrieve a FastBufferReader from a serializer where IsReader = false");
}
public FastBufferWriter GetFastBufferWriter()
{
return m_Writer;
}
public void SerializeValue(ref string s, bool oneByteChars = false)
{
m_Writer.WriteValueSafe(s, oneByteChars);
}
public void SerializeValue<T>(ref T[] array) where T : unmanaged
{
m_Writer.WriteValueSafe(array);
}
public void SerializeValue(ref byte value)
{
m_Writer.WriteByteSafe(value);
}
public void SerializeValue<T>(ref T value) where T : unmanaged
{
m_Writer.WriteValueSafe(value);
}
public void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new()
{
m_Writer.WriteNetworkSerializable(value);
}
public bool PreCheck(int amount)
{
return m_Writer.TryBeginWrite(amount);
}
public void SerializeValuePreChecked(ref string s, bool oneByteChars = false)
{
m_Writer.WriteValue(s, oneByteChars);
}
public void SerializeValuePreChecked<T>(ref T[] array) where T : unmanaged
{
m_Writer.WriteValue(array);
}
public void SerializeValuePreChecked(ref byte value)
{
m_Writer.WriteByte(value);
}
public void SerializeValuePreChecked<T>(ref T value) where T : unmanaged
{
m_Writer.WriteValue(value);
}
}
}

View File

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

View File

@@ -0,0 +1,477 @@
using System;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Unity.Netcode
{
/// <summary>
/// Utility class for packing values in serialization.
/// </summary>
public static class BytePacker
{
#if UNITY_NETCODE_DEBUG_NO_PACKING
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValuePacked<T>(FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value);
#else
/// <summary>
/// Write a packed enum value.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">The value to write</param>
/// <typeparam name="TEnum">An enum type</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void WriteValuePacked<TEnum>(FastBufferWriter writer, TEnum value) where TEnum : unmanaged, Enum
{
TEnum enumValue = value;
switch (sizeof(TEnum))
{
case sizeof(int):
WriteValuePacked(writer, *(int*)&enumValue);
break;
case sizeof(byte):
WriteValuePacked(writer, *(byte*)&enumValue);
break;
case sizeof(short):
WriteValuePacked(writer, *(short*)&enumValue);
break;
case sizeof(long):
WriteValuePacked(writer, *(long*)&enumValue);
break;
}
}
/// <summary>
/// Write single-precision floating point value to the buffer as a varint
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, float value)
{
WriteUInt32Packed(writer, ToUint(value));
}
/// <summary>
/// Write double-precision floating point value to the buffer as a varint
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, double value)
{
WriteUInt64Packed(writer, ToUlong(value));
}
/// <summary>
/// Write a byte to the buffer.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, byte value) => writer.WriteByteSafe(value);
/// <summary>
/// Write a signed byte to the buffer.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, sbyte value) => writer.WriteByteSafe((byte)value);
/// <summary>
/// Write a bool to the buffer.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, bool value) => writer.WriteValueSafe(value);
/// <summary>
/// Write a signed short (Int16) as a ZigZag encoded varint to the buffer.
/// WARNING: If the value you're writing is > 2287, this will use MORE space
/// (3 bytes instead of 2), and if your value is > 240 you'll get no savings at all.
/// Only use this if you're certain your value will be small.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, short value) => WriteUInt32Packed(writer, (ushort)Arithmetic.ZigZagEncode(value));
/// <summary>
/// Write an unsigned short (UInt16) as a varint to the buffer.
/// WARNING: If the value you're writing is > 2287, this will use MORE space
/// (3 bytes instead of 2), and if your value is > 240 you'll get no savings at all.
/// Only use this if you're certain your value will be small.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, ushort value) => WriteUInt32Packed(writer, value);
/// <summary>
/// Write a two-byte character as a varint to the buffer.
/// WARNING: If the value you're writing is > 2287, this will use MORE space
/// (3 bytes instead of 2), and if your value is > 240 you'll get no savings at all.
/// Only use this if you're certain your value will be small.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="c">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, char c) => WriteUInt32Packed(writer, c);
/// <summary>
/// Write a signed int (Int32) as a ZigZag encoded varint to the buffer.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, int value) => WriteUInt32Packed(writer, (uint)Arithmetic.ZigZagEncode(value));
/// <summary>
/// Write an unsigned int (UInt32) to the buffer.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, uint value) => WriteUInt32Packed(writer, value);
/// <summary>
/// Write an unsigned long (UInt64) to the buffer.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, ulong value) => WriteUInt64Packed(writer, value);
/// <summary>
/// Write a signed long (Int64) as a ZigZag encoded varint to the buffer.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, long value) => WriteUInt64Packed(writer, Arithmetic.ZigZagEncode(value));
/// <summary>
/// Convenience method that writes two packed Vector3 from the ray to the buffer
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="ray">Ray to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, Ray ray)
{
WriteValuePacked(writer, ray.origin);
WriteValuePacked(writer, ray.direction);
}
/// <summary>
/// Convenience method that writes two packed Vector2 from the ray to the buffer
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="ray2d">Ray2D to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, Ray2D ray2d)
{
WriteValuePacked(writer, ray2d.origin);
WriteValuePacked(writer, ray2d.direction);
}
/// <summary>
/// Convenience method that writes four varint floats from the color to the buffer
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="color">Color to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, Color color)
{
WriteValuePacked(writer, color.r);
WriteValuePacked(writer, color.g);
WriteValuePacked(writer, color.b);
WriteValuePacked(writer, color.a);
}
/// <summary>
/// Convenience method that writes four varint floats from the color to the buffer
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="color">Color to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, Color32 color)
{
WriteValuePacked(writer, color.r);
WriteValuePacked(writer, color.g);
WriteValuePacked(writer, color.b);
WriteValuePacked(writer, color.a);
}
/// <summary>
/// Convenience method that writes two varint floats from the vector to the buffer
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="vector2">Vector to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, Vector2 vector2)
{
WriteValuePacked(writer, vector2.x);
WriteValuePacked(writer, vector2.y);
}
/// <summary>
/// Convenience method that writes three varint floats from the vector to the buffer
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="vector3">Vector to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, Vector3 vector3)
{
WriteValuePacked(writer, vector3.x);
WriteValuePacked(writer, vector3.y);
WriteValuePacked(writer, vector3.z);
}
/// <summary>
/// Convenience method that writes four varint floats from the vector to the buffer
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="vector4">Vector to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, Vector4 vector4)
{
WriteValuePacked(writer, vector4.x);
WriteValuePacked(writer, vector4.y);
WriteValuePacked(writer, vector4.z);
WriteValuePacked(writer, vector4.w);
}
/// <summary>
/// Writes the rotation to the buffer.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="rotation">Rotation to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, Quaternion rotation)
{
WriteValuePacked(writer, rotation.x);
WriteValuePacked(writer, rotation.y);
WriteValuePacked(writer, rotation.z);
WriteValuePacked(writer, rotation.w);
}
/// <summary>
/// Writes a string in a packed format
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="s">The value to pack</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteValuePacked(FastBufferWriter writer, string s)
{
WriteValuePacked(writer, (uint)s.Length);
int target = s.Length;
for (int i = 0; i < target; ++i)
{
WriteValuePacked(writer, s[i]);
}
}
#endif
#if UNITY_NETCODE_DEBUG_NO_PACKING
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteValueBitPacked<T>(FastBufferWriter writer, T value) where T: unmanaged => writer.WriteValueSafe(value);
#else
/// <summary>
/// Writes a 14-bit signed short to the buffer in a bit-encoded packed format.
/// The first bit indicates whether the value is 1 byte or 2.
/// The sign bit takes up another bit.
/// That leaves 14 bits for the value.
/// A value greater than 2^14-1 or less than -2^14 will throw an exception in editor and development builds.
/// In release builds builds the exception is not thrown and the value is truncated by losing its two
/// most significant bits after zig-zag encoding.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</param>
public static void WriteValueBitPacked(FastBufferWriter writer, short value) => WriteValueBitPacked(writer, (ushort)Arithmetic.ZigZagEncode(value));
/// <summary>
/// Writes a 15-bit unsigned short to the buffer in a bit-encoded packed format.
/// The first bit indicates whether the value is 1 byte or 2.
/// That leaves 15 bits for the value.
/// A value greater than 2^15-1 will throw an exception in editor and development builds.
/// In release builds builds the exception is not thrown and the value is truncated by losing its
/// most significant bit.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</param>
public static void WriteValueBitPacked(FastBufferWriter writer, ushort value)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (value >= 0b1000_0000_0000_0000)
{
throw new ArgumentException("BitPacked ushorts must be <= 15 bits");
}
#endif
if (value <= 0b0111_1111)
{
if (!writer.TryBeginWriteInternal(1))
{
throw new OverflowException("Writing past the end of the buffer");
}
writer.WriteByte((byte)(value << 1));
return;
}
if (!writer.TryBeginWriteInternal(2))
{
throw new OverflowException("Writing past the end of the buffer");
}
writer.WriteValue((ushort)((value << 1) | 0b1));
}
/// <summary>
/// Writes a 29-bit signed int to the buffer in a bit-encoded packed format.
/// The first two bits indicate whether the value is 1, 2, 3, or 4 bytes.
/// The sign bit takes up another bit.
/// That leaves 29 bits for the value.
/// A value greater than 2^29-1 or less than -2^29 will throw an exception in editor and development builds.
/// In release builds builds the exception is not thrown and the value is truncated by losing its three
/// most significant bits after zig-zag encoding.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</param>
public static void WriteValueBitPacked(FastBufferWriter writer, int value) => WriteValueBitPacked(writer, (uint)Arithmetic.ZigZagEncode(value));
/// <summary>
/// Writes a 30-bit unsigned int to the buffer in a bit-encoded packed format.
/// The first two bits indicate whether the value is 1, 2, 3, or 4 bytes.
/// That leaves 30 bits for the value.
/// A value greater than 2^30-1 will throw an exception in editor and development builds.
/// In release builds builds the exception is not thrown and the value is truncated by losing its two
/// most significant bits.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</param>
public static void WriteValueBitPacked(FastBufferWriter writer, uint value)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (value >= 0b0100_0000_0000_0000_0000_0000_0000_0000)
{
throw new ArgumentException("BitPacked uints must be <= 30 bits");
}
#endif
value <<= 2;
var numBytes = BitCounter.GetUsedByteCount(value);
if (!writer.TryBeginWriteInternal(numBytes))
{
throw new OverflowException("Writing past the end of the buffer");
}
writer.WritePartialValue(value | (uint)(numBytes - 1), numBytes);
}
/// <summary>
/// Writes a 60-bit signed long to the buffer in a bit-encoded packed format.
/// The first three bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, or 8 bytes.
/// The sign bit takes up another bit.
/// That leaves 60 bits for the value.
/// A value greater than 2^60-1 or less than -2^60 will throw an exception in editor and development builds.
/// In release builds builds the exception is not thrown and the value is truncated by losing its four
/// most significant bits after zig-zag encoding.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</param>
public static void WriteValueBitPacked(FastBufferWriter writer, long value) => WriteValueBitPacked(writer, Arithmetic.ZigZagEncode(value));
/// <summary>
/// Writes a 61-bit unsigned long to the buffer in a bit-encoded packed format.
/// The first three bits indicate whether the value is 1, 2, 3, 4, 5, 6, 7, or 8 bytes.
/// That leaves 31 bits for the value.
/// A value greater than 2^61-1 will throw an exception in editor and development builds.
/// In release builds builds the exception is not thrown and the value is truncated by losing its three
/// most significant bits.
/// </summary>
/// <param name="writer">The writer to write to</param>
/// <param name="value">The value to pack</param>
public static void WriteValueBitPacked(FastBufferWriter writer, ulong value)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (value >= 0b0010_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000)
{
throw new ArgumentException("BitPacked ulongs must be <= 61 bits");
}
#endif
value <<= 3;
var numBytes = BitCounter.GetUsedByteCount(value);
if (!writer.TryBeginWriteInternal(numBytes))
{
throw new OverflowException("Writing past the end of the buffer");
}
writer.WritePartialValue(value | (uint)(numBytes - 1), numBytes);
}
#endif
private static void WriteUInt64Packed(FastBufferWriter writer, ulong value)
{
if (value <= 240)
{
writer.WriteByteSafe((byte)value);
return;
}
if (value <= 2287)
{
writer.WriteByteSafe((byte)(((value - 240) >> 8) + 241));
writer.WriteByteSafe((byte)(value - 240));
return;
}
var writeBytes = BitCounter.GetUsedByteCount(value);
if (!writer.TryBeginWriteInternal(writeBytes + 1))
{
throw new OverflowException("Writing past the end of the buffer");
}
writer.WriteByte((byte)(247 + writeBytes));
writer.WritePartialValue(value, writeBytes);
}
// Looks like the same code as WriteUInt64Packed?
// It's actually different because it will call the more efficient 32-bit version
// of BytewiseUtility.GetUsedByteCount().
private static void WriteUInt32Packed(FastBufferWriter writer, uint value)
{
if (value <= 240)
{
writer.WriteByteSafe((byte)value);
return;
}
if (value <= 2287)
{
writer.WriteByteSafe((byte)(((value - 240) >> 8) + 241));
writer.WriteByteSafe((byte)(value - 240));
return;
}
var writeBytes = BitCounter.GetUsedByteCount(value);
if (!writer.TryBeginWriteInternal(writeBytes + 1))
{
throw new OverflowException("Writing past the end of the buffer");
}
writer.WriteByte((byte)(247 + writeBytes));
writer.WritePartialValue(value, writeBytes);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe uint ToUint<T>(T value) where T : unmanaged
{
uint* asUint = (uint*)&value;
return *asUint;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe ulong ToUlong<T>(T value) where T : unmanaged
{
ulong* asUlong = (ulong*)&value;
return *asUlong;
}
}
}

View File

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

View File

@@ -0,0 +1,558 @@
using System;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Unity.Netcode
{
public static class ByteUnpacker
{
#if UNITY_NETCODE_DEBUG_NO_PACKING
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValuePacked<T>(FastBufferReader reader, out T value) where T: unmanaged => reader.ReadValueSafe(out value);
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void ReadValuePacked<TEnum>(FastBufferReader reader, out TEnum value) where TEnum : unmanaged, Enum
{
switch (sizeof(TEnum))
{
case sizeof(int):
ReadValuePacked(reader, out int asInt);
value = *(TEnum*)&asInt;
break;
case sizeof(byte):
ReadValuePacked(reader, out byte asByte);
value = *(TEnum*)&asByte;
break;
case sizeof(short):
ReadValuePacked(reader, out short asShort);
value = *(TEnum*)&asShort;
break;
case sizeof(long):
ReadValuePacked(reader, out long asLong);
value = *(TEnum*)&asLong;
break;
default:
throw new InvalidOperationException("Enum is a size that cannot exist?!");
}
}
/// <summary>
/// Read single-precision floating point value from the stream as a varint
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out float value)
{
ReadUInt32Packed(reader, out uint asUInt);
value = ToSingle(asUInt);
}
/// <summary>
/// Read double-precision floating point value from the stream as a varint
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out double value)
{
ReadUInt64Packed(reader, out ulong asULong);
value = ToDouble(asULong);
}
/// <summary>
/// Read a byte from the stream.
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out byte value) => reader.ReadByteSafe(out value);
/// <summary>
/// Read a signed byte from the stream.
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out sbyte value)
{
reader.ReadByteSafe(out byte byteVal);
value = (sbyte)byteVal;
}
/// <summary>
/// Read a boolean from the stream.
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out bool value) => reader.ReadValueSafe(out value);
/// <summary>
/// Read an usigned short (Int16) as a varint from the stream.
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out short value)
{
ReadUInt32Packed(reader, out uint readValue);
value = (short)Arithmetic.ZigZagDecode(readValue);
}
/// <summary>
/// Read an unsigned short (UInt16) as a varint from the stream.
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out ushort value)
{
ReadUInt32Packed(reader, out uint readValue);
value = (ushort)readValue;
}
/// <summary>
/// Read a two-byte character as a varint from the stream.
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="c">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out char c)
{
ReadUInt32Packed(reader, out uint readValue);
c = (char)readValue;
}
/// <summary>
/// Read a signed int (Int32) as a ZigZag encoded varint from the stream.
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out int value)
{
ReadUInt32Packed(reader, out uint readValue);
value = (int)Arithmetic.ZigZagDecode(readValue);
}
/// <summary>
/// Read an unsigned int (UInt32) from the stream.
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out uint value) => ReadUInt32Packed(reader, out value);
/// <summary>
/// Read an unsigned long (UInt64) from the stream.
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out ulong value) => ReadUInt64Packed(reader, out value);
/// <summary>
/// Read a signed long (Int64) as a ZigZag encoded varint from the stream.
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">Value to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out long value)
{
ReadUInt64Packed(reader, out ulong readValue);
value = Arithmetic.ZigZagDecode(readValue);
}
/// <summary>
/// Convenience method that reads two packed Vector3 from the ray from the stream
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="ray">Ray to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out Ray ray)
{
ReadValuePacked(reader, out Vector3 origin);
ReadValuePacked(reader, out Vector3 direction);
ray = new Ray(origin, direction);
}
/// <summary>
/// Convenience method that reads two packed Vector2 from the ray from the stream
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="ray2d">Ray2D to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out Ray2D ray2d)
{
ReadValuePacked(reader, out Vector2 origin);
ReadValuePacked(reader, out Vector2 direction);
ray2d = new Ray2D(origin, direction);
}
/// <summary>
/// Convenience method that reads four varint floats from the color from the stream
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="color">Color to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out Color color)
{
color = new Color();
ReadValuePacked(reader, out color.r);
ReadValuePacked(reader, out color.g);
ReadValuePacked(reader, out color.b);
ReadValuePacked(reader, out color.a);
}
/// <summary>
/// Convenience method that reads four varint floats from the color from the stream
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="color">Color to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out Color32 color)
{
color = new Color32();
ReadValuePacked(reader, out color.r);
ReadValuePacked(reader, out color.g);
ReadValuePacked(reader, out color.b);
ReadValuePacked(reader, out color.a);
}
/// <summary>
/// Convenience method that reads two varint floats from the vector from the stream
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="vector2">Vector to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out Vector2 vector2)
{
vector2 = new Vector2();
ReadValuePacked(reader, out vector2.x);
ReadValuePacked(reader, out vector2.y);
}
/// <summary>
/// Convenience method that reads three varint floats from the vector from the stream
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="vector3">Vector to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out Vector3 vector3)
{
vector3 = new Vector3();
ReadValuePacked(reader, out vector3.x);
ReadValuePacked(reader, out vector3.y);
ReadValuePacked(reader, out vector3.z);
}
/// <summary>
/// Convenience method that reads four varint floats from the vector from the stream
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="vector4">Vector to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out Vector4 vector4)
{
vector4 = new Vector4();
ReadValuePacked(reader, out vector4.x);
ReadValuePacked(reader, out vector4.y);
ReadValuePacked(reader, out vector4.z);
ReadValuePacked(reader, out vector4.w);
}
/// <summary>
/// Reads the rotation from the stream.
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="rotation">Rotation to read</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadValuePacked(FastBufferReader reader, out Quaternion rotation)
{
rotation = new Quaternion();
ReadValuePacked(reader, out rotation.x);
ReadValuePacked(reader, out rotation.y);
ReadValuePacked(reader, out rotation.z);
ReadValuePacked(reader, out rotation.w);
}
/// <summary>
/// Reads a string in a packed format
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="s"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void ReadValuePacked(FastBufferReader reader, out string s)
{
ReadValuePacked(reader, out uint length);
s = "".PadRight((int)length);
int target = s.Length;
fixed (char* c = s)
{
for (int i = 0; i < target; ++i)
{
ReadValuePacked(reader, out c[i]);
}
}
}
#endif
#if UNITY_NETCODE_DEBUG_NO_PACKING
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReadValueBitPacked<T>(FastBufferReader reader, T value) where T: unmanaged => reader.ReadValueSafe(out value);
#else
/// <summary>
/// Read a bit-packed 14-bit signed short from the stream.
/// See BytePacker.cs for a description of the format.
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">The value to read</param>
public static void ReadValueBitPacked(FastBufferReader reader, out short value)
{
ReadValueBitPacked(reader, out ushort readValue);
value = (short)Arithmetic.ZigZagDecode(readValue);
}
/// <summary>
/// Read a bit-packed 15-bit unsigned short from the stream.
/// See BytePacker.cs for a description of the format.
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">The value to read</param>
public static unsafe void ReadValueBitPacked(FastBufferReader reader, out ushort value)
{
ushort returnValue = 0;
byte* ptr = ((byte*)&returnValue);
byte* data = reader.GetUnsafePtrAtCurrentPosition();
int numBytes = (data[0] & 0b1) + 1;
if (!reader.TryBeginReadInternal(numBytes))
{
throw new OverflowException("Reading past the end of the buffer");
}
reader.MarkBytesRead(numBytes);
switch (numBytes)
{
case 1:
*ptr = *data;
break;
case 2:
*ptr = *data;
*(ptr + 1) = *(data + 1);
break;
default:
throw new InvalidOperationException("Could not read bit-packed value: impossible byte count");
}
value = (ushort)(returnValue >> 1);
}
/// <summary>
/// Read a bit-packed 29-bit signed int from the stream.
/// See BytePacker.cs for a description of the format.
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">The value to read</param>
public static void ReadValueBitPacked(FastBufferReader reader, out int value)
{
ReadValueBitPacked(reader, out uint readValue);
value = (int)Arithmetic.ZigZagDecode(readValue);
}
/// <summary>
/// Read a bit-packed 30-bit unsigned int from the stream.
/// See BytePacker.cs for a description of the format.
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">The value to read</param>
public static unsafe void ReadValueBitPacked(FastBufferReader reader, out uint value)
{
uint returnValue = 0;
byte* ptr = ((byte*)&returnValue);
byte* data = reader.GetUnsafePtrAtCurrentPosition();
int numBytes = (data[0] & 0b11) + 1;
if (!reader.TryBeginReadInternal(numBytes))
{
throw new OverflowException("Reading past the end of the buffer");
}
reader.MarkBytesRead(numBytes);
switch (numBytes)
{
case 1:
*ptr = *data;
break;
case 2:
*ptr = *data;
*(ptr + 1) = *(data + 1);
break;
case 3:
*ptr = *data;
*(ptr + 1) = *(data + 1);
*(ptr + 2) = *(data + 2);
break;
case 4:
*ptr = *data;
*(ptr + 1) = *(data + 1);
*(ptr + 2) = *(data + 2);
*(ptr + 3) = *(data + 3);
break;
}
value = returnValue >> 2;
}
/// <summary>
/// Read a bit-packed 60-bit signed long from the stream.
/// See BytePacker.cs for a description of the format.
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">The value to read</param>
public static void ReadValueBitPacked(FastBufferReader reader, out long value)
{
ReadValueBitPacked(reader, out ulong readValue);
value = Arithmetic.ZigZagDecode(readValue);
}
/// <summary>
/// Read a bit-packed 61-bit signed long from the stream.
/// See BytePacker.cs for a description of the format.
/// </summary>
/// <param name="reader">The reader to read from</param>
/// <param name="value">The value to read</param>
public static unsafe void ReadValueBitPacked(FastBufferReader reader, out ulong value)
{
ulong returnValue = 0;
byte* ptr = ((byte*)&returnValue);
byte* data = reader.GetUnsafePtrAtCurrentPosition();
int numBytes = (data[0] & 0b111) + 1;
if (!reader.TryBeginReadInternal(numBytes))
{
throw new OverflowException("Reading past the end of the buffer");
}
reader.MarkBytesRead(numBytes);
switch (numBytes)
{
case 1:
*ptr = *data;
break;
case 2:
*ptr = *data;
*(ptr + 1) = *(data + 1);
break;
case 3:
*ptr = *data;
*(ptr + 1) = *(data + 1);
*(ptr + 2) = *(data + 2);
break;
case 4:
*ptr = *data;
*(ptr + 1) = *(data + 1);
*(ptr + 2) = *(data + 2);
*(ptr + 3) = *(data + 3);
break;
case 5:
*ptr = *data;
*(ptr + 1) = *(data + 1);
*(ptr + 2) = *(data + 2);
*(ptr + 3) = *(data + 3);
*(ptr + 4) = *(data + 4);
break;
case 6:
*ptr = *data;
*(ptr + 1) = *(data + 1);
*(ptr + 2) = *(data + 2);
*(ptr + 3) = *(data + 3);
*(ptr + 4) = *(data + 4);
*(ptr + 5) = *(data + 5);
break;
case 7:
*ptr = *data;
*(ptr + 1) = *(data + 1);
*(ptr + 2) = *(data + 2);
*(ptr + 3) = *(data + 3);
*(ptr + 4) = *(data + 4);
*(ptr + 5) = *(data + 5);
*(ptr + 6) = *(data + 6);
break;
case 8:
*ptr = *data;
*(ptr + 1) = *(data + 1);
*(ptr + 2) = *(data + 2);
*(ptr + 3) = *(data + 3);
*(ptr + 4) = *(data + 4);
*(ptr + 5) = *(data + 5);
*(ptr + 6) = *(data + 6);
*(ptr + 7) = *(data + 7);
break;
}
value = returnValue >> 3;
}
#endif
private static void ReadUInt64Packed(FastBufferReader reader, out ulong value)
{
reader.ReadByteSafe(out byte firstByte);
if (firstByte <= 240)
{
value = firstByte;
return;
}
if (firstByte <= 248)
{
reader.ReadByteSafe(out byte secondByte);
value = 240UL + ((firstByte - 241UL) << 8) + secondByte;
return;
}
var numBytes = firstByte - 247;
if (!reader.TryBeginReadInternal(numBytes))
{
throw new OverflowException("Reading past the end of the buffer");
}
reader.ReadPartialValue(out value, numBytes);
}
private static void ReadUInt32Packed(FastBufferReader reader, out uint value)
{
reader.ReadByteSafe(out byte firstByte);
if (firstByte <= 240)
{
value = firstByte;
return;
}
if (firstByte <= 248)
{
reader.ReadByteSafe(out byte secondByte);
value = 240U + ((firstByte - 241U) << 8) + secondByte;
return;
}
var numBytes = firstByte - 247;
if (!reader.TryBeginReadInternal(numBytes))
{
throw new OverflowException("Reading past the end of the buffer");
}
reader.ReadPartialValue(out value, numBytes);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe float ToSingle<T>(T value) where T : unmanaged
{
float* asFloat = (float*)&value;
return *asFloat;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe double ToDouble<T>(T value) where T : unmanaged
{
double* asDouble = (double*)&value;
return *asDouble;
}
}
}

View File

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

View File

@@ -0,0 +1,773 @@
using System;
using System.Runtime.CompilerServices;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Netcode
{
public struct FastBufferReader : IDisposable
{
internal struct ReaderHandle
{
internal unsafe byte* BufferPointer;
internal int Position;
internal int Length;
internal Allocator Allocator;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
internal int AllowedReadMark;
internal bool InBitwiseContext;
#endif
}
internal readonly unsafe ReaderHandle* Handle;
/// <summary>
/// Get the current read position
/// </summary>
public unsafe int Position
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Handle->Position;
}
/// <summary>
/// Get the total length of the buffer
/// </summary>
public unsafe int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Handle->Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal unsafe void CommitBitwiseReads(int amount)
{
Handle->Position += amount;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
Handle->InBitwiseContext = false;
#endif
}
private static unsafe ReaderHandle* CreateHandle(byte* buffer, int length, int offset, Allocator allocator)
{
ReaderHandle* readerHandle = null;
if (allocator == Allocator.None)
{
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf<byte>(), Allocator.Temp);
readerHandle->BufferPointer = buffer;
readerHandle->Position = offset;
}
else
{
readerHandle = (ReaderHandle*)UnsafeUtility.Malloc(sizeof(ReaderHandle) + length, UnsafeUtility.AlignOf<byte>(), allocator);
UnsafeUtility.MemCpy(readerHandle + 1, buffer + offset, length);
readerHandle->BufferPointer = (byte*)(readerHandle + 1);
readerHandle->Position = 0;
}
readerHandle->Length = length;
readerHandle->Allocator = allocator;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
readerHandle->AllowedReadMark = 0;
readerHandle->InBitwiseContext = false;
#endif
return readerHandle;
}
/// <summary>
/// Create a FastBufferReader from a NativeArray.
///
/// A new buffer will be created using the given allocator and the value will be copied in.
/// FastBufferReader will then own the data.
///
/// The exception to this is when the allocator passed in is Allocator.None. In this scenario,
/// ownership of the data remains with the caller and the reader will point at it directly.
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
/// the context in which it was created (it should neither be returned from that function nor
/// stored anywhere in heap memory).
/// </summary>
/// <param name="buffer"></param>
/// <param name="allocator"></param>
/// <param name="length"></param>
/// <param name="offset"></param>
public unsafe FastBufferReader(NativeArray<byte> buffer, Allocator allocator, int length = -1, int offset = 0)
{
Handle = CreateHandle((byte*)buffer.GetUnsafePtr(), Math.Max(1, length == -1 ? buffer.Length : length), offset, allocator);
}
/// <summary>
/// Create a FastBufferReader from an ArraySegment.
///
/// A new buffer will be created using the given allocator and the value will be copied in.
/// FastBufferReader will then own the data.
///
/// Allocator.None is not supported for byte[]. If you need this functionality, use a fixed() block
/// and ensure the FastBufferReader isn't used outside that block.
/// </summary>
/// <param name="buffer">The buffer to copy from</param>
/// <param name="allocator">The allocator to use</param>
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
/// <param name="offset">The offset of the buffer to start copying from</param>
public unsafe FastBufferReader(ArraySegment<byte> buffer, Allocator allocator, int length = -1, int offset = 0)
{
if (allocator == Allocator.None)
{
throw new NotSupportedException("Allocator.None cannot be used with managed source buffers.");
}
fixed (byte* data = buffer.Array)
{
Handle = CreateHandle(data, Math.Max(1, length == -1 ? buffer.Count : length), offset, allocator);
}
}
/// <summary>
/// Create a FastBufferReader from an existing byte array.
///
/// A new buffer will be created using the given allocator and the value will be copied in.
/// FastBufferReader will then own the data.
///
/// Allocator.None is not supported for byte[]. If you need this functionality, use a fixed() block
/// and ensure the FastBufferReader isn't used outside that block.
/// </summary>
/// <param name="buffer">The buffer to copy from</param>
/// <param name="allocator">The allocator to use</param>
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
/// <param name="offset">The offset of the buffer to start copying from</param>
public unsafe FastBufferReader(byte[] buffer, Allocator allocator, int length = -1, int offset = 0)
{
if (allocator == Allocator.None)
{
throw new NotSupportedException("Allocator.None cannot be used with managed source buffers.");
}
fixed (byte* data = buffer)
{
Handle = CreateHandle(data, Math.Max(1, length == -1 ? buffer.Length : length), offset, allocator);
}
}
/// <summary>
/// Create a FastBufferReader from an existing byte buffer.
///
/// A new buffer will be created using the given allocator and the value will be copied in.
/// FastBufferReader will then own the data.
///
/// The exception to this is when the allocator passed in is Allocator.None. In this scenario,
/// ownership of the data remains with the caller and the reader will point at it directly.
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
/// the context in which it was created (it should neither be returned from that function nor
/// stored anywhere in heap memory).
/// </summary>
/// <param name="buffer">The buffer to copy from</param>
/// <param name="allocator">The allocator to use</param>
/// <param name="length">The number of bytes to copy</param>
/// <param name="offset">The offset of the buffer to start copying from</param>
public unsafe FastBufferReader(byte* buffer, Allocator allocator, int length, int offset = 0)
{
Handle = CreateHandle(buffer, Math.Max(1, length), offset, allocator);
}
/// <summary>
/// Create a FastBufferReader from a FastBufferWriter.
///
/// A new buffer will be created using the given allocator and the value will be copied in.
/// FastBufferReader will then own the data.
///
/// The exception to this is when the allocator passed in is Allocator.None. In this scenario,
/// ownership of the data remains with the caller and the reader will point at it directly.
/// When created with Allocator.None, FastBufferReader will allocate some internal data using
/// Allocator.Temp, so it should be treated as if it's a ref struct and not allowed to outlive
/// the context in which it was created (it should neither be returned from that function nor
/// stored anywhere in heap memory).
/// </summary>
/// <param name="writer">The writer to copy from</param>
/// <param name="allocator">The allocator to use</param>
/// <param name="length">The number of bytes to copy (all if this is -1)</param>
/// <param name="offset">The offset of the buffer to start copying from</param>
public unsafe FastBufferReader(FastBufferWriter writer, Allocator allocator, int length = -1, int offset = 0)
{
Handle = CreateHandle(writer.GetUnsafePtr(), Math.Max(1, length == -1 ? writer.Length : length), offset, allocator);
}
/// <summary>
/// Frees the allocated buffer
/// </summary>
public unsafe void Dispose()
{
UnsafeUtility.Free(Handle, Handle->Allocator);
}
/// <summary>
/// Move the read position in the stream
/// </summary>
/// <param name="where">Absolute value to move the position to, truncated to Length</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void Seek(int where)
{
Handle->Position = Math.Min(Length, where);
}
/// <summary>
/// Mark that some bytes are going to be read via GetUnsafePtr().
/// </summary>
/// <param name="amount">Amount that will be read</param>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="OverflowException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal unsafe void MarkBytesRead(int amount)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
}
if (Handle->Position + amount > Handle->AllowedReadMark)
{
throw new OverflowException("Attempted to read without first calling TryBeginRead()");
}
#endif
Handle->Position += amount;
}
/// <summary>
/// Retrieve a BitReader to be able to perform bitwise operations on the buffer.
/// No bytewise operations can be performed on the buffer until bitReader.Dispose() has been called.
/// At the end of the operation, FastBufferReader will remain byte-aligned.
/// </summary>
/// <returns>A BitReader</returns>
public unsafe BitReader EnterBitwiseContext()
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
Handle->InBitwiseContext = true;
#endif
return new BitReader(this);
}
/// <summary>
/// Allows faster serialization by batching bounds checking.
/// When you know you will be reading multiple fields back-to-back and you know the total size,
/// you can call TryBeginRead() once on the total size, and then follow it with calls to
/// ReadValue() instead of ReadValueSafe() for faster serialization.
///
/// Unsafe read operations will throw OverflowException in editor and development builds if you
/// go past the point you've marked using TryBeginRead(). In release builds, OverflowException will not be thrown
/// for performance reasons, since the point of using TryBeginRead is to avoid bounds checking in the following
/// operations in release builds.
/// </summary>
/// <param name="bytes">Amount of bytes to read</param>
/// <returns>True if the read is allowed, false otherwise</returns>
/// <exception cref="InvalidOperationException">If called while in a bitwise context</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe bool TryBeginRead(int bytes)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
}
#endif
if (Handle->Position + bytes > Handle->Length)
{
return false;
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
Handle->AllowedReadMark = Handle->Position + bytes;
#endif
return true;
}
/// <summary>
/// Allows faster serialization by batching bounds checking.
/// When you know you will be reading multiple fields back-to-back and you know the total size,
/// you can call TryBeginRead() once on the total size, and then follow it with calls to
/// ReadValue() instead of ReadValueSafe() for faster serialization.
///
/// Unsafe read operations will throw OverflowException in editor and development builds if you
/// go past the point you've marked using TryBeginRead(). In release builds, OverflowException will not be thrown
/// for performance reasons, since the point of using TryBeginRead is to avoid bounds checking in the following
/// operations in release builds.
/// </summary>
/// <param name="value">The value you want to read</param>
/// <returns>True if the read is allowed, false otherwise</returns>
/// <exception cref="InvalidOperationException">If called while in a bitwise context</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe bool TryBeginReadValue<T>(in T value) where T : unmanaged
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
}
#endif
int len = sizeof(T);
if (Handle->Position + len > Handle->Length)
{
return false;
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
Handle->AllowedReadMark = Handle->Position + len;
#endif
return true;
}
/// <summary>
/// Internal version of TryBeginRead.
/// Differs from TryBeginRead only in that it won't ever move the AllowedReadMark backward.
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal unsafe bool TryBeginReadInternal(int bytes)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
}
#endif
if (Handle->Position + bytes > Handle->Length)
{
return false;
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->Position + bytes > Handle->AllowedReadMark)
{
Handle->AllowedReadMark = Handle->Position + bytes;
}
#endif
return true;
}
/// <summary>
/// Returns an array representation of the underlying byte buffer.
/// !!Allocates a new array!!
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe byte[] ToArray()
{
byte[] ret = new byte[Length];
fixed (byte* b = ret)
{
UnsafeUtility.MemCpy(b, Handle->BufferPointer, Length);
}
return ret;
}
/// <summary>
/// Gets a direct pointer to the underlying buffer
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe byte* GetUnsafePtr()
{
return Handle->BufferPointer;
}
/// <summary>
/// Gets a direct pointer to the underlying buffer at the current read position
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe byte* GetUnsafePtrAtCurrentPosition()
{
return Handle->BufferPointer + Handle->Position;
}
/// <summary>
/// Read an INetworkSerializable
/// </summary>
/// <param name="value">INetworkSerializable instance</param>
/// <typeparam name="T"></typeparam>
/// <exception cref="NotImplementedException"></exception>
public void ReadNetworkSerializable<T>(out T value) where T : INetworkSerializable, new()
{
value = new T();
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(this));
value.NetworkSerialize(bufferSerializer);
}
/// <summary>
/// Read an array of INetworkSerializables
/// </summary>
/// <param name="value">INetworkSerializable instance</param>
/// <typeparam name="T"></typeparam>
/// <exception cref="NotImplementedException"></exception>
public void ReadNetworkSerializable<T>(out T[] value) where T : INetworkSerializable, new()
{
ReadValueSafe(out int size);
value = new T[size];
for (var i = 0; i < size; ++i)
{
ReadNetworkSerializable(out value[i]);
}
}
/// <summary>
/// Reads a string
/// NOTE: ALLOCATES
/// </summary>
/// <param name="s">Stores the read string</param>
/// <param name="oneByteChars">Whether or not to use one byte per character. This will only allow ASCII</param>
public unsafe void ReadValue(out string s, bool oneByteChars = false)
{
ReadValue(out uint length);
s = "".PadRight((int)length);
int target = s.Length;
fixed (char* native = s)
{
if (oneByteChars)
{
for (int i = 0; i < target; ++i)
{
ReadByte(out byte b);
native[i] = (char)b;
}
}
else
{
ReadBytes((byte*)native, target * sizeof(char));
}
}
}
/// <summary>
/// Reads a string.
/// NOTE: ALLOCATES
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="s">Stores the read string</param>
/// <param name="oneByteChars">Whether or not to use one byte per character. This will only allow ASCII</param>
public unsafe void ReadValueSafe(out string s, bool oneByteChars = false)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
}
#endif
if (!TryBeginReadInternal(sizeof(uint)))
{
throw new OverflowException("Reading past the end of the buffer");
}
ReadValue(out uint length);
if (!TryBeginReadInternal((int)length * (oneByteChars ? 1 : sizeof(char))))
{
throw new OverflowException("Reading past the end of the buffer");
}
s = "".PadRight((int)length);
int target = s.Length;
fixed (char* native = s)
{
if (oneByteChars)
{
for (int i = 0; i < target; ++i)
{
ReadByte(out byte b);
native[i] = (char)b;
}
}
else
{
ReadBytes((byte*)native, target * sizeof(char));
}
}
}
/// <summary>
/// Writes an unmanaged array
/// NOTE: ALLOCATES
/// </summary>
/// <param name="array">Stores the read array</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ReadValue<T>(out T[] array) where T : unmanaged
{
ReadValue(out int sizeInTs);
int sizeInBytes = sizeInTs * sizeof(T);
array = new T[sizeInTs];
fixed (T* native = array)
{
byte* bytes = (byte*)(native);
ReadBytes(bytes, sizeInBytes);
}
}
/// <summary>
/// Reads an unmanaged array
/// NOTE: ALLOCATES
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="array">Stores the read array</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ReadValueSafe<T>(out T[] array) where T : unmanaged
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
}
#endif
if (!TryBeginReadInternal(sizeof(int)))
{
throw new OverflowException("Reading past the end of the buffer");
}
ReadValue(out int sizeInTs);
int sizeInBytes = sizeInTs * sizeof(T);
if (!TryBeginReadInternal(sizeInBytes))
{
throw new OverflowException("Reading past the end of the buffer");
}
array = new T[sizeInTs];
fixed (T* native = array)
{
byte* bytes = (byte*)(native);
ReadBytes(bytes, sizeInBytes);
}
}
/// <summary>
/// Read a partial value. The value is zero-initialized and then the specified number of bytes is read into it.
/// </summary>
/// <param name="value">Value to read</param>
/// <param name="bytesToRead">Number of bytes</param>
/// <param name="offsetBytes">Offset into the value to write the bytes</param>
/// <typeparam name="T"></typeparam>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="OverflowException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ReadPartialValue<T>(out T value, int bytesToRead, int offsetBytes = 0) where T : unmanaged
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
}
if (Handle->Position + bytesToRead > Handle->AllowedReadMark)
{
throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginRead)}()");
}
#endif
var val = new T();
byte* ptr = ((byte*)&val) + offsetBytes;
byte* bufferPointer = Handle->BufferPointer + Handle->Position;
UnsafeUtility.MemCpy(ptr, bufferPointer, bytesToRead);
Handle->Position += bytesToRead;
value = val;
}
/// <summary>
/// Read a byte to the stream.
/// </summary>
/// <param name="value">Stores the read value</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ReadByte(out byte value)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
}
if (Handle->Position + 1 > Handle->AllowedReadMark)
{
throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginRead)}()");
}
#endif
value = Handle->BufferPointer[Handle->Position++];
}
/// <summary>
/// Read a byte to the stream.
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">Stores the read value</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ReadByteSafe(out byte value)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
}
#endif
if (!TryBeginReadInternal(1))
{
throw new OverflowException("Reading past the end of the buffer");
}
value = Handle->BufferPointer[Handle->Position++];
}
/// <summary>
/// Read multiple bytes to the stream
/// </summary>
/// <param name="value">Pointer to the destination buffer</param>
/// <param name="size">Number of bytes to read - MUST BE &lt;= BUFFER SIZE</param>
/// <param name="offset">Offset of the byte buffer to store into</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ReadBytes(byte* value, int size, int offset = 0)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
}
if (Handle->Position + size > Handle->AllowedReadMark)
{
throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginRead)}()");
}
#endif
UnsafeUtility.MemCpy(value + offset, (Handle->BufferPointer + Handle->Position), size);
Handle->Position += size;
}
/// <summary>
/// Read multiple bytes to the stream
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">Pointer to the destination buffer</param>
/// <param name="size">Number of bytes to read - MUST BE &lt;= BUFFER SIZE</param>
/// <param name="offset">Offset of the byte buffer to store into</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ReadBytesSafe(byte* value, int size, int offset = 0)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
}
#endif
if (!TryBeginReadInternal(size))
{
throw new OverflowException("Reading past the end of the buffer");
}
UnsafeUtility.MemCpy(value + offset, (Handle->BufferPointer + Handle->Position), size);
Handle->Position += size;
}
/// <summary>
/// Read multiple bytes from the stream
/// </summary>
/// <param name="value">Pointer to the destination buffer</param>
/// <param name="size">Number of bytes to read - MUST BE &lt;= BUFFER SIZE</param>
/// <param name="offset">Offset of the byte buffer to store into</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ReadBytes(ref byte[] value, int size, int offset = 0)
{
fixed (byte* ptr = value)
{
ReadBytes(ptr, size, offset);
}
}
/// <summary>
/// Read multiple bytes from the stream
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">Pointer to the destination buffer</param>
/// <param name="size">Number of bytes to read - MUST BE &lt;= BUFFER SIZE</param>
/// <param name="offset">Offset of the byte buffer to store into</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ReadBytesSafe(ref byte[] value, int size, int offset = 0)
{
fixed (byte* ptr = value)
{
ReadBytesSafe(ptr, size, offset);
}
}
/// <summary>
/// Read a value of any unmanaged type to the buffer.
/// It will be copied from the buffer exactly as it existed in memory on the writing end.
/// </summary>
/// <param name="value">The read value</param>
/// <typeparam name="T">Any unmanaged type</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ReadValue<T>(out T value) where T : unmanaged
{
int len = sizeof(T);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
}
if (Handle->Position + len > Handle->AllowedReadMark)
{
throw new OverflowException($"Attempted to read without first calling {nameof(TryBeginRead)}()");
}
#endif
fixed (T* ptr = &value)
{
UnsafeUtility.MemCpy((byte*)ptr, Handle->BufferPointer + Handle->Position, len);
}
Handle->Position += len;
}
/// <summary>
/// Read a value of any unmanaged type to the buffer.
/// It will be copied from the buffer exactly as it existed in memory on the writing end.
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple reads at once by calling TryBeginRead.
/// </summary>
/// <param name="value">The read value</param>
/// <typeparam name="T">Any unmanaged type</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void ReadValueSafe<T>(out T value) where T : unmanaged
{
int len = sizeof(T);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferReader in bytewise mode while in a bitwise context.");
}
#endif
if (!TryBeginReadInternal(len))
{
throw new OverflowException("Reading past the end of the buffer");
}
fixed (T* ptr = &value)
{
UnsafeUtility.MemCpy((byte*)ptr, Handle->BufferPointer + Handle->Position, len);
}
Handle->Position += len;
}
}
}

View File

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

View File

@@ -0,0 +1,822 @@
using System;
using System.Runtime.CompilerServices;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Netcode
{
public struct FastBufferWriter : IDisposable
{
internal struct WriterHandle
{
internal unsafe byte* BufferPointer;
internal int Position;
internal int Length;
internal int Capacity;
internal int MaxCapacity;
internal Allocator Allocator;
internal bool BufferGrew;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
internal int AllowedWriteMark;
internal bool InBitwiseContext;
#endif
}
internal readonly unsafe WriterHandle* Handle;
/// <summary>
/// The current write position
/// </summary>
public unsafe int Position
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Handle->Position;
}
/// <summary>
/// The current total buffer size
/// </summary>
public unsafe int Capacity
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Handle->Capacity;
}
/// <summary>
/// The maximum possible total buffer size
/// </summary>
public unsafe int MaxCapacity
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Handle->MaxCapacity;
}
/// <summary>
/// The total amount of bytes that have been written to the stream
/// </summary>
public unsafe int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Handle->Position > Handle->Length ? Handle->Position : Handle->Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal unsafe void CommitBitwiseWrites(int amount)
{
Handle->Position += amount;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
Handle->InBitwiseContext = false;
#endif
}
/// <summary>
/// Create a FastBufferWriter.
/// </summary>
/// <param name="size">Size of the buffer to create</param>
/// <param name="allocator">Allocator to use in creating it</param>
/// <param name="maxSize">Maximum size the buffer can grow to. If less than size, buffer cannot grow.</param>
public unsafe FastBufferWriter(int size, Allocator allocator, int maxSize = -1)
{
Handle = (WriterHandle*)UnsafeUtility.Malloc(sizeof(WriterHandle) + size, UnsafeUtility.AlignOf<WriterHandle>(), allocator);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
UnsafeUtility.MemSet(Handle, 0, sizeof(WriterHandle) + size);
#endif
Handle->BufferPointer = (byte*)(Handle + 1);
Handle->Position = 0;
Handle->Length = 0;
Handle->Capacity = size;
Handle->Allocator = allocator;
Handle->MaxCapacity = maxSize < size ? size : maxSize;
Handle->BufferGrew = false;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
Handle->AllowedWriteMark = 0;
Handle->InBitwiseContext = false;
#endif
}
/// <summary>
/// Frees the allocated buffer
/// </summary>
public unsafe void Dispose()
{
if (Handle->BufferGrew)
{
UnsafeUtility.Free(Handle->BufferPointer, Handle->Allocator);
}
UnsafeUtility.Free(Handle, Handle->Allocator);
}
/// <summary>
/// Move the write position in the stream.
/// Note that moving forward past the current length will extend the buffer's Length value even if you don't write.
/// </summary>
/// <param name="where">Absolute value to move the position to, truncated to Capacity</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void Seek(int where)
{
// This avoids us having to synchronize length all the time.
// Writing things is a much more common operation than seeking
// or querying length. The length here is a high watermark of
// what's been written. So before we seek, if the current position
// is greater than the length, we update that watermark.
// When querying length later, we'll return whichever of the two
// values is greater, thus if we write past length, length increases
// because position increases, and if we seek backward, length remembers
// the position it was in.
// Seeking forward will not update the length.
where = Math.Min(where, Handle->Capacity);
if (Handle->Position > Handle->Length && where < Handle->Position)
{
Handle->Length = Handle->Position;
}
Handle->Position = where;
}
/// <summary>
/// Truncate the stream by setting Length to the specified value.
/// If Position is greater than the specified value, it will be moved as well.
/// </summary>
/// <param name="where">The value to truncate to. If -1, the current position will be used.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void Truncate(int where = -1)
{
if (where == -1)
{
where = Position;
}
if (Handle->Position > where)
{
Handle->Position = where;
}
if (Handle->Length > where)
{
Handle->Length = where;
}
}
/// <summary>
/// Retrieve a BitWriter to be able to perform bitwise operations on the buffer.
/// No bytewise operations can be performed on the buffer until bitWriter.Dispose() has been called.
/// At the end of the operation, FastBufferWriter will remain byte-aligned.
/// </summary>
/// <returns>A BitWriter</returns>
public unsafe BitWriter EnterBitwiseContext()
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
Handle->InBitwiseContext = true;
#endif
return new BitWriter(this);
}
internal unsafe void Grow(int additionalSizeRequired)
{
var desiredSize = Handle->Capacity * 2;
while (desiredSize < Position + additionalSizeRequired)
{
desiredSize *= 2;
}
var newSize = Math.Min(desiredSize, Handle->MaxCapacity);
byte* newBuffer = (byte*)UnsafeUtility.Malloc(newSize, UnsafeUtility.AlignOf<byte>(), Handle->Allocator);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
UnsafeUtility.MemSet(newBuffer, 0, sizeof(WriterHandle) + newSize);
#endif
UnsafeUtility.MemCpy(newBuffer, Handle->BufferPointer, Length);
if (Handle->BufferGrew)
{
UnsafeUtility.Free(Handle->BufferPointer, Handle->Allocator);
}
Handle->BufferGrew = true;
Handle->BufferPointer = newBuffer;
Handle->Capacity = newSize;
}
/// <summary>
/// Allows faster serialization by batching bounds checking.
/// When you know you will be writing multiple fields back-to-back and you know the total size,
/// you can call TryBeginWrite() once on the total size, and then follow it with calls to
/// WriteValue() instead of WriteValueSafe() for faster serialization.
///
/// Unsafe write operations will throw OverflowException in editor and development builds if you
/// go past the point you've marked using TryBeginWrite(). In release builds, OverflowException will not be thrown
/// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following
/// operations in release builds.
/// </summary>
/// <param name="bytes">Amount of bytes to write</param>
/// <returns>True if the write is allowed, false otherwise</returns>
/// <exception cref="InvalidOperationException">If called while in a bitwise context</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe bool TryBeginWrite(int bytes)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
}
#endif
if (Handle->Position + bytes > Handle->Capacity)
{
if (Handle->Position + bytes > Handle->MaxCapacity)
{
return false;
}
if (Handle->Capacity < Handle->MaxCapacity)
{
Grow(bytes);
}
else
{
return false;
}
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
Handle->AllowedWriteMark = Handle->Position + bytes;
#endif
return true;
}
/// <summary>
/// Allows faster serialization by batching bounds checking.
/// When you know you will be writing multiple fields back-to-back and you know the total size,
/// you can call TryBeginWrite() once on the total size, and then follow it with calls to
/// WriteValue() instead of WriteValueSafe() for faster serialization.
///
/// Unsafe write operations will throw OverflowException in editor and development builds if you
/// go past the point you've marked using TryBeginWrite(). In release builds, OverflowException will not be thrown
/// for performance reasons, since the point of using TryBeginWrite is to avoid bounds checking in the following
/// operations in release builds. Instead, attempting to write past the marked position in release builds
/// will write to random memory and cause undefined behavior, likely including instability and crashes.
/// </summary>
/// <param name="value">The value you want to write</param>
/// <returns>True if the write is allowed, false otherwise</returns>
/// <exception cref="InvalidOperationException">If called while in a bitwise context</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe bool TryBeginWriteValue<T>(in T value) where T : unmanaged
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
}
#endif
int len = sizeof(T);
if (Handle->Position + len > Handle->Capacity)
{
if (Handle->Position + len > Handle->MaxCapacity)
{
return false;
}
if (Handle->Capacity < Handle->MaxCapacity)
{
Grow(len);
}
else
{
return false;
}
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
Handle->AllowedWriteMark = Handle->Position + len;
#endif
return true;
}
/// <summary>
/// Internal version of TryBeginWrite.
/// Differs from TryBeginWrite only in that it won't ever move the AllowedWriteMark backward.
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe bool TryBeginWriteInternal(int bytes)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
}
#endif
if (Handle->Position + bytes > Handle->Capacity)
{
if (Handle->Position + bytes > Handle->MaxCapacity)
{
return false;
}
if (Handle->Capacity < Handle->MaxCapacity)
{
Grow(bytes);
}
else
{
return false;
}
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->Position + bytes > Handle->AllowedWriteMark)
{
Handle->AllowedWriteMark = Handle->Position + bytes;
}
#endif
return true;
}
/// <summary>
/// Returns an array representation of the underlying byte buffer.
/// !!Allocates a new array!!
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe byte[] ToArray()
{
byte[] ret = new byte[Length];
fixed (byte* b = ret)
{
UnsafeUtility.MemCpy(b, Handle->BufferPointer, Length);
}
return ret;
}
/// <summary>
/// Gets a direct pointer to the underlying buffer
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe byte* GetUnsafePtr()
{
return Handle->BufferPointer;
}
/// <summary>
/// Gets a direct pointer to the underlying buffer at the current read position
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe byte* GetUnsafePtrAtCurrentPosition()
{
return Handle->BufferPointer + Handle->Position;
}
/// <summary>
/// Get the required size to write a string
/// </summary>
/// <param name="s">The string to write</param>
/// <param name="oneByteChars">Whether or not to use one byte per character. This will only allow ASCII</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetWriteSize(string s, bool oneByteChars = false)
{
return sizeof(int) + s.Length * (oneByteChars ? sizeof(byte) : sizeof(char));
}
/// <summary>
/// Write an INetworkSerializable
/// </summary>
/// <param name="value">The value to write</param>
/// <typeparam name="T"></typeparam>
public void WriteNetworkSerializable<T>(in T value) where T : INetworkSerializable
{
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(this));
value.NetworkSerialize(bufferSerializer);
}
/// <summary>
/// Write an array of INetworkSerializables
/// </summary>
/// <param name="array">The value to write</param>
/// <param name="count"></param>
/// <param name="offset"></param>
/// <typeparam name="T"></typeparam>
public void WriteNetworkSerializable<T>(INetworkSerializable[] array, int count = -1, int offset = 0) where T : INetworkSerializable
{
int sizeInTs = count != -1 ? count : array.Length - offset;
WriteValueSafe(sizeInTs);
foreach (var item in array)
{
WriteNetworkSerializable(item);
}
}
/// <summary>
/// Writes a string
/// </summary>
/// <param name="s">The string to write</param>
/// <param name="oneByteChars">Whether or not to use one byte per character. This will only allow ASCII</param>
public unsafe void WriteValue(string s, bool oneByteChars = false)
{
WriteValue((uint)s.Length);
int target = s.Length;
if (oneByteChars)
{
for (int i = 0; i < target; ++i)
{
WriteByte((byte)s[i]);
}
}
else
{
fixed (char* native = s)
{
WriteBytes((byte*)native, target * sizeof(char));
}
}
}
/// <summary>
/// Writes a string
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="s">The string to write</param>
/// <param name="oneByteChars">Whether or not to use one byte per character. This will only allow ASCII</param>
public unsafe void WriteValueSafe(string s, bool oneByteChars = false)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
}
#endif
int sizeInBytes = GetWriteSize(s, oneByteChars);
if (!TryBeginWriteInternal(sizeInBytes))
{
throw new OverflowException("Writing past the end of the buffer");
}
WriteValue((uint)s.Length);
int target = s.Length;
if (oneByteChars)
{
for (int i = 0; i < target; ++i)
{
WriteByte((byte)s[i]);
}
}
else
{
fixed (char* native = s)
{
WriteBytes((byte*)native, target * sizeof(char));
}
}
}
/// <summary>
/// Get the required size to write an unmanaged array
/// </summary>
/// <param name="array">The array to write</param>
/// <param name="count">The amount of elements to write</param>
/// <param name="offset">Where in the array to start</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int GetWriteSize<T>(T[] array, int count = -1, int offset = 0) where T : unmanaged
{
int sizeInTs = count != -1 ? count : array.Length - offset;
int sizeInBytes = sizeInTs * sizeof(T);
return sizeof(int) + sizeInBytes;
}
/// <summary>
/// Writes an unmanaged array
/// </summary>
/// <param name="array">The array to write</param>
/// <param name="count">The amount of elements to write</param>
/// <param name="offset">Where in the array to start</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void WriteValue<T>(T[] array, int count = -1, int offset = 0) where T : unmanaged
{
int sizeInTs = count != -1 ? count : array.Length - offset;
int sizeInBytes = sizeInTs * sizeof(T);
WriteValue(sizeInTs);
fixed (T* native = array)
{
byte* bytes = (byte*)(native + offset);
WriteBytes(bytes, sizeInBytes);
}
}
/// <summary>
/// Writes an unmanaged array
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="array">The array to write</param>
/// <param name="count">The amount of elements to write</param>
/// <param name="offset">Where in the array to start</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void WriteValueSafe<T>(T[] array, int count = -1, int offset = 0) where T : unmanaged
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
}
#endif
int sizeInTs = count != -1 ? count : array.Length - offset;
int sizeInBytes = sizeInTs * sizeof(T);
if (!TryBeginWriteInternal(sizeInBytes + sizeof(int)))
{
throw new OverflowException("Writing past the end of the buffer");
}
WriteValue(sizeInTs);
fixed (T* native = array)
{
byte* bytes = (byte*)(native + offset);
WriteBytes(bytes, sizeInBytes);
}
}
/// <summary>
/// Write a partial value. The specified number of bytes is written from the value and the rest is ignored.
/// </summary>
/// <param name="value">Value to write</param>
/// <param name="bytesToWrite">Number of bytes</param>
/// <param name="offsetBytes">Offset into the value to begin reading the bytes</param>
/// <typeparam name="T"></typeparam>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="OverflowException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void WritePartialValue<T>(T value, int bytesToWrite, int offsetBytes = 0) where T : unmanaged
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
}
if (Handle->Position + bytesToWrite > Handle->AllowedWriteMark)
{
throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}()");
}
#endif
byte* ptr = ((byte*)&value) + offsetBytes;
byte* bufferPointer = Handle->BufferPointer + Handle->Position;
UnsafeUtility.MemCpy(bufferPointer, ptr, bytesToWrite);
Handle->Position += bytesToWrite;
}
/// <summary>
/// Write a byte to the stream.
/// </summary>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void WriteByte(byte value)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
}
if (Handle->Position + 1 > Handle->AllowedWriteMark)
{
throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}()");
}
#endif
Handle->BufferPointer[Handle->Position++] = value;
}
/// <summary>
/// Write a byte to the stream.
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">Value to write</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void WriteByteSafe(byte value)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
}
#endif
if (!TryBeginWriteInternal(1))
{
throw new OverflowException("Writing past the end of the buffer");
}
Handle->BufferPointer[Handle->Position++] = value;
}
/// <summary>
/// Write multiple bytes to the stream
/// </summary>
/// <param name="value">Value to write</param>
/// <param name="size">Number of bytes to write</param>
/// <param name="offset">Offset into the buffer to begin writing</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void WriteBytes(byte* value, int size, int offset = 0)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
}
if (Handle->Position + size > Handle->AllowedWriteMark)
{
throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}()");
}
#endif
UnsafeUtility.MemCpy((Handle->BufferPointer + Handle->Position), value + offset, size);
Handle->Position += size;
}
/// <summary>
/// Write multiple bytes to the stream
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">Value to write</param>
/// <param name="size">Number of bytes to write</param>
/// <param name="offset">Offset into the buffer to begin writing</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void WriteBytesSafe(byte* value, int size, int offset = 0)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
}
#endif
if (!TryBeginWriteInternal(size))
{
throw new OverflowException("Writing past the end of the buffer");
}
UnsafeUtility.MemCpy((Handle->BufferPointer + Handle->Position), value + offset, size);
Handle->Position += size;
}
/// <summary>
/// Write multiple bytes to the stream
/// </summary>
/// <param name="value">Value to write</param>
/// <param name="size">Number of bytes to write</param>
/// <param name="offset">Offset into the buffer to begin writing</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void WriteBytes(byte[] value, int size = -1, int offset = 0)
{
fixed (byte* ptr = value)
{
WriteBytes(ptr, size == -1 ? value.Length : size, offset);
}
}
/// <summary>
/// Write multiple bytes to the stream
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">Value to write</param>
/// <param name="size">Number of bytes to write</param>
/// <param name="offset">Offset into the buffer to begin writing</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void WriteBytesSafe(byte[] value, int size = -1, int offset = 0)
{
fixed (byte* ptr = value)
{
WriteBytesSafe(ptr, size == -1 ? value.Length : size, offset);
}
}
/// <summary>
/// Copy the contents of this writer into another writer.
/// The contents will be copied from the beginning of this writer to its current position.
/// They will be copied to the other writer starting at the other writer's current position.
/// </summary>
/// <param name="other">Writer to copy to</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void CopyTo(FastBufferWriter other)
{
other.WriteBytes(Handle->BufferPointer, Handle->Position);
}
/// <summary>
/// Copy the contents of another writer into this writer.
/// The contents will be copied from the beginning of the other writer to its current position.
/// They will be copied to this writer starting at this writer's current position.
/// </summary>
/// <param name="other">Writer to copy to</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void CopyFrom(FastBufferWriter other)
{
WriteBytes(other.Handle->BufferPointer, other.Handle->Position);
}
/// <summary>
/// Get the size required to write an unmanaged value
/// </summary>
/// <param name="value"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int GetWriteSize<T>(in T value) where T : unmanaged
{
return sizeof(T);
}
/// <summary>
/// Get the size required to write an unmanaged value of type T
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static unsafe int GetWriteSize<T>() where T : unmanaged
{
return sizeof(T);
}
/// <summary>
/// Write a value of any unmanaged type (including unmanaged structs) to the buffer.
/// It will be copied into the buffer exactly as it exists in memory.
/// </summary>
/// <param name="value">The value to copy</param>
/// <typeparam name="T">Any unmanaged type</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void WriteValue<T>(in T value) where T : unmanaged
{
int len = sizeof(T);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
}
if (Handle->Position + len > Handle->AllowedWriteMark)
{
throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}()");
}
#endif
fixed (T* ptr = &value)
{
UnsafeUtility.MemCpy(Handle->BufferPointer + Handle->Position, (byte*)ptr, len);
}
Handle->Position += len;
}
/// <summary>
/// Write a value of any unmanaged type (including unmanaged structs) to the buffer.
/// It will be copied into the buffer exactly as it exists in memory.
///
/// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking
/// for multiple writes at once by calling TryBeginWrite.
/// </summary>
/// <param name="value">The value to copy</param>
/// <typeparam name="T">Any unmanaged type</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void WriteValueSafe<T>(in T value) where T : unmanaged
{
int len = sizeof(T);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (Handle->InBitwiseContext)
{
throw new InvalidOperationException(
"Cannot use BufferWriter in bytewise mode while in a bitwise context.");
}
#endif
if (!TryBeginWriteInternal(len))
{
throw new OverflowException("Writing past the end of the buffer");
}
fixed (T* ptr = &value)
{
UnsafeUtility.MemCpy(Handle->BufferPointer + Handle->Position, (byte*)ptr, len);
}
Handle->Position += len;
}
}
}

View File

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

View File

@@ -0,0 +1,18 @@
namespace Unity.Netcode
{
/// <summary>
/// Interface for implementing custom serializable types.
/// </summary>
public interface INetworkSerializable
{
/// <summary>
/// Provides bi-directional serialization to read and write the desired data to serialize this type.
/// </summary>
/// <param name="serializer">The serializer to use to read and write the data.</param>
/// <typeparam name="T">
/// Either BufferSerializerReader or BufferSerializerWriter, depending whether the serializer
/// is in read mode or write mode.
/// </typeparam>
void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter;
}
}

View File

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

View File

@@ -0,0 +1,25 @@
namespace Unity.Netcode
{
public interface IReaderWriter
{
bool IsReader { get; }
bool IsWriter { get; }
FastBufferReader GetFastBufferReader();
FastBufferWriter GetFastBufferWriter();
void SerializeValue(ref string s, bool oneByteChars = false);
void SerializeValue<T>(ref T[] array) where T : unmanaged;
void SerializeValue(ref byte value);
void SerializeValue<T>(ref T value) where T : unmanaged;
// Has to have a different name to avoid conflicting with "where T: unmananged"
void SerializeNetworkSerializable<T>(ref T value) where T : INetworkSerializable, new();
bool PreCheck(int amount);
void SerializeValuePreChecked(ref string s, bool oneByteChars = false);
void SerializeValuePreChecked<T>(ref T[] array) where T : unmanaged;
void SerializeValuePreChecked(ref byte value);
void SerializeValuePreChecked<T>(ref T value) where T : unmanaged;
}
}

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0a4a44dfad228154db7945948c115efa
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
using System.Runtime.InteropServices;
namespace Unity.Netcode
{
[StructLayout(LayoutKind.Explicit)]
internal struct ByteBool
{
[FieldOffset(0)]
public bool BoolValue;
[FieldOffset(0)]
public byte ByteValue;
public byte Collapse() =>
ByteValue = (byte)((
// Collapse all bits to position 1 and reassign as bit
(ByteValue >> 7) |
(ByteValue >> 6) |
(ByteValue >> 5) |
(ByteValue >> 4) |
(ByteValue >> 3) |
(ByteValue >> 2) |
(ByteValue >> 1) |
ByteValue
) & 1);
public byte Collapse(bool b)
{
BoolValue = b;
return Collapse();
}
}
}

View File

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

View File

@@ -0,0 +1,26 @@
using System.Runtime.InteropServices;
namespace Unity.Netcode
{
/// <summary>
/// A struct with a explicit memory layout. The struct has 4 fields. float,uint,double and ulong.
/// Every field has the same starting point in memory. If you insert a float value, it can be extracted as a uint.
/// This is to allow for lockless and garbage free conversion from float to uint and double to ulong.
/// This allows for VarInt encoding and other integer encodings.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
internal struct UIntFloat
{
[FieldOffset(0)]
public float FloatValue;
[FieldOffset(0)]
public uint UIntValue;
[FieldOffset(0)]
public double DoubleValue;
[FieldOffset(0)]
public ulong ULongValue;
}
}

View File

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

View File

@@ -0,0 +1,103 @@
using System;
using System.Runtime.CompilerServices;
namespace Unity.Netcode
{
/// <summary>
/// A helper struct for serializing <see cref="NetworkBehaviour"/>s over the network. Can be used in RPCs and <see cref="NetworkVariable{T}"/>.
/// Note: network ids get recycled by the NetworkManager after a while. So a reference pointing to
/// </summary>
public struct NetworkBehaviourReference : INetworkSerializable, IEquatable<NetworkBehaviourReference>
{
private NetworkObjectReference m_NetworkObjectReference;
private ushort m_NetworkBehaviourId;
/// <summary>
/// Creates a new instance of the <see cref="NetworkBehaviourReference{T}"/> struct.
/// </summary>
/// <param name="networkBehaviour">The <see cref="NetworkBehaviour"/> to reference.</param>
/// <exception cref="ArgumentException"></exception>
public NetworkBehaviourReference(NetworkBehaviour networkBehaviour)
{
if (networkBehaviour == null)
{
throw new ArgumentNullException(nameof(networkBehaviour));
}
if (networkBehaviour.NetworkObject == null)
{
throw new ArgumentException($"Cannot create {nameof(NetworkBehaviourReference)} from {nameof(NetworkBehaviour)} without a {nameof(NetworkObject)}.");
}
m_NetworkObjectReference = networkBehaviour.NetworkObject;
m_NetworkBehaviourId = networkBehaviour.NetworkBehaviourId;
}
/// <summary>
/// Tries to get the <see cref="NetworkBehaviour"/> referenced by this reference.
/// </summary>
/// <param name="networkBehaviour">The <see cref="NetworkBehaviour"/> which was found. Null if the corresponding <see cref="NetworkObject"/> was not found.</param>
/// <param name="networkManager">The networkmanager. Uses <see cref="NetworkManager.Singleton"/> to resolve if null.</param>
/// <returns>True if the <see cref="NetworkBehaviour"/> was found; False if the <see cref="NetworkBehaviour"/> was not found. This can happen if the corresponding <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns>
public bool TryGet(out NetworkBehaviour networkBehaviour, NetworkManager networkManager = null)
{
networkBehaviour = GetInternal(this, null);
return networkBehaviour != null;
}
/// <summary>
/// Tries to get the <see cref="NetworkBehaviour"/> referenced by this reference.
/// </summary>
/// <param name="networkBehaviour">The <see cref="NetworkBehaviour"/> which was found. Null if the corresponding <see cref="NetworkObject"/> was not found.</param>
/// <param name="networkManager">The networkmanager. Uses <see cref="NetworkManager.Singleton"/> to resolve if null.</param>
/// <typeparam name="T">The type of the networkBehaviour for convenience.</typeparam>
/// <returns>True if the <see cref="NetworkBehaviour"/> was found; False if the <see cref="NetworkBehaviour"/> was not found. This can happen if the corresponding <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns>
public bool TryGet<T>(out T networkBehaviour, NetworkManager networkManager = null) where T : NetworkBehaviour
{
networkBehaviour = (T)GetInternal(this, null);
return networkBehaviour != null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static NetworkBehaviour GetInternal(NetworkBehaviourReference networkBehaviourRef, NetworkManager networkManager = null)
{
if (networkBehaviourRef.m_NetworkObjectReference.TryGet(out NetworkObject networkObject, networkManager))
{
return networkObject.GetNetworkBehaviourAtOrderIndex(networkBehaviourRef.m_NetworkBehaviourId);
}
return null;
}
/// <inheritdoc/>
public bool Equals(NetworkBehaviourReference other)
{
return m_NetworkObjectReference.Equals(other.m_NetworkObjectReference) && m_NetworkBehaviourId == other.m_NetworkBehaviourId;
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
return obj is NetworkBehaviourReference other && Equals(other);
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
return (m_NetworkObjectReference.GetHashCode() * 397) ^ m_NetworkBehaviourId.GetHashCode();
}
}
/// <inheritdoc/>
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
m_NetworkObjectReference.NetworkSerialize(serializer);
serializer.SerializeValue(ref m_NetworkBehaviourId);
}
public static implicit operator NetworkBehaviour(NetworkBehaviourReference networkBehaviourRef) => GetInternal(networkBehaviourRef);
public static implicit operator NetworkBehaviourReference(NetworkBehaviour networkBehaviour) => new NetworkBehaviourReference(networkBehaviour);
}
}

View File

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

View File

@@ -0,0 +1,131 @@
using System;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Unity.Netcode
{
/// <summary>
/// A helper struct for serializing <see cref="NetworkObject"/>s over the network. Can be used in RPCs and <see cref="NetworkVariable{T}"/>.
/// </summary>
public struct NetworkObjectReference : INetworkSerializable, IEquatable<NetworkObjectReference>
{
private ulong m_NetworkObjectId;
/// <summary>
/// The <see cref="NetworkObject.NetworkObjectId"/> of the referenced <see cref="NetworkObject"/>.
/// </summary>
public ulong NetworkObjectId
{
get => m_NetworkObjectId;
internal set => m_NetworkObjectId = value;
}
/// <summary>
/// Creates a new instance of the <see cref="NetworkObjectReference"/> struct.
/// </summary>
/// <param name="networkObject">The <see cref="NetworkObject"/> to reference.</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public NetworkObjectReference(NetworkObject networkObject)
{
if (networkObject == null)
{
throw new ArgumentNullException(nameof(networkObject));
}
if (networkObject.IsSpawned == false)
{
throw new ArgumentException($"{nameof(NetworkObjectReference)} can only be created from spawned {nameof(NetworkObject)}s.");
}
m_NetworkObjectId = networkObject.NetworkObjectId;
}
/// <summary>
/// Creates a new instance of the <see cref="NetworkObjectReference"/> struct.
/// </summary>
/// <param name="gameObject">The GameObject from which the <see cref="NetworkObject"/> component will be referenced.</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public NetworkObjectReference(GameObject gameObject)
{
if (gameObject == null)
{
throw new ArgumentNullException(nameof(gameObject));
}
var networkObject = gameObject.GetComponent<NetworkObject>();
if (networkObject == null)
{
throw new ArgumentException($"Cannot create {nameof(NetworkObjectReference)} from {nameof(GameObject)} without a {nameof(NetworkObject)} component.");
}
if (networkObject.IsSpawned == false)
{
throw new ArgumentException($"{nameof(NetworkObjectReference)} can only be created from spawned {nameof(NetworkObject)}s.");
}
m_NetworkObjectId = networkObject.NetworkObjectId;
}
/// <summary>
/// Tries to get the <see cref="NetworkObject"/> referenced by this reference.
/// </summary>
/// <param name="networkObject">The <see cref="NetworkObject"/> which was found. Null if no object was found.</param>
/// <param name="networkManager">The networkmanager. Uses <see cref="NetworkManager.Singleton"/> to resolve if null.</param>
/// <returns>True if the <see cref="NetworkObject"/> was found; False if the <see cref="NetworkObject"/> was not found. This can happen if the <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns>
public bool TryGet(out NetworkObject networkObject, NetworkManager networkManager = null)
{
networkObject = Resolve(this, networkManager);
return networkObject != null;
}
/// <summary>
/// Resolves the corresponding <see cref="NetworkObject"/> for this reference.
/// </summary>
/// <param name="networkObjectRef">The reference.</param>
/// <param name="networkManager">The networkmanager. Uses <see cref="NetworkManager.Singleton"/> to resolve if null.</param>
/// <returns>The resolves <see cref="NetworkObject"/>. Returns null if the networkobject was not found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static NetworkObject Resolve(NetworkObjectReference networkObjectRef, NetworkManager networkManager = null)
{
networkManager = networkManager != null ? networkManager : NetworkManager.Singleton;
networkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectRef.m_NetworkObjectId, out NetworkObject networkObject);
return networkObject;
}
/// <inheritdoc/>
public bool Equals(NetworkObjectReference other)
{
return m_NetworkObjectId == other.m_NetworkObjectId;
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
return obj is NetworkObjectReference other && Equals(other);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return m_NetworkObjectId.GetHashCode();
}
/// <inheritdoc/>
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref m_NetworkObjectId);
}
public static implicit operator NetworkObject(NetworkObjectReference networkObjectRef) => Resolve(networkObjectRef);
public static implicit operator NetworkObjectReference(NetworkObject networkObject) => new NetworkObjectReference(networkObject);
public static implicit operator GameObject(NetworkObjectReference networkObjectRef) => Resolve(networkObjectRef).gameObject;
public static implicit operator NetworkObjectReference(GameObject gameObject) => new NetworkObjectReference(gameObject);
}
}

View File

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