VRChat 非公式日本語ドキュメント

バイトおよびビット演算

情報

このページでは、バイナリデータを扱う際の低レイヤーな概念について解説しています。上級者向けのコンテンツです。

Udonにおいて、プリミティブなデータ型に対して値を保持したまま型変換(「アンセーフキャスト」)を行うには、DataTokenのBitcast メソッドを使用できます。

生バイナリデータを操作するための標準的なC#クラスもいくつか利用可能です。これにはBitConverterBuffer が含まれます。

コード例

バイトおよびビット演算の例:基本的なシリアライザー
using System;
using System.Text;
using UdonSharp;
using UnityEngine;
using VRC.SDK3.Data;

public class BitConverterExample : UdonSharpBehaviour
{
void Start()
{
//Create test data
int originalInt = 63;
double originalDouble = 734531.433d;
string originalString = "Test string";
float[] originalFloatArray = new float[] { 543, 12.6f, 63.1231f };

//Serialize and then deserialize
byte[] serialized = Serialize(originalInt, originalDouble, originalString, originalFloatArray);
Deserialize(serialized, out int newInt, out double newDouble, out string newString, out float[] newFloatArray);

//Print the results to see if everything matches
Debug.Log($"{originalInt} - {newInt}");
Debug.Log($"{originalDouble} - {newDouble}");
Debug.Log($"{originalString} - {newString}");
Debug.Log($"{originalFloatArray.Length} - {newFloatArray.Length}");
for (int i = 0; i < originalFloatArray.Length && i < newFloatArray.Length; i++)
{
Debug.Log($"{originalFloatArray[i]} - {newFloatArray[i]}");
}

//For individual values we can also use DataToken Bitcasting to get bit access
double doubleValue = 123.456d;
DataToken doubleToken = new DataToken(doubleValue);
//We used ulong because it has the same size as a double (8 bytes)
DataToken ulongToken = doubleToken.Bitcast(TokenType.ULong);
DataToken resultDoubleToken = ulongToken.Bitcast(TokenType.Double);
Debug.Log($"{doubleToken} - 0x{ulongToken:02X} - {resultDoubleToken}");
}

/// <summary>
/// An example function which serializes a pre-determined set of data into a byte array
/// </summary>
/// <param name="intValue">Integer which will be encoded into the output</param>
/// <param name="doubleValue">Double which will be encoded into the output</param>
/// <param name="stringValue">String which will be encoded into the output</param>
/// <param name="floatArrayValues">Float array which will be encoded into the output</param>
/// <returns></returns>
byte[] Serialize(int intValue, double doubleValue, string stringValue, float[] floatArrayValues)
{
int size = 0;
byte[] intBytes = BitConverter.GetBytes(intValue); //Convert int to bytes
size += intBytes.Length;

byte[] doubleBytes = BitConverter.GetBytes(doubleValue); //Convert int to bytes
size += doubleBytes.Length;

byte[] stringBytes = Encoding.UTF8.GetBytes(stringValue); //Convert string to bytes
size += stringBytes.Length;
Debug.Log($"String byte length {stringBytes.Length}");

byte[] stringLengthBytes = BitConverter.GetBytes(stringBytes.Length); //Convert string length to bytes
size += stringLengthBytes.Length;

byte[] floatArrayLengthBytes = BitConverter.GetBytes(Buffer.ByteLength(floatArrayValues));
size += floatArrayLengthBytes.Length;

//It is not necessary to convert the float array into a byte array because we can BlockCopy it directly
size += Buffer.ByteLength(floatArrayValues);

byte[] output = new byte[size]; //Allocate an array of the correct size that should fit all of our items
int offset = 0;

Buffer.BlockCopy(intBytes, 0, output, offset, intBytes.Length); //Write int - this should take up 4 bytes
offset += intBytes.Length; //Increment offset so the next item can write to the correct location

Buffer.BlockCopy(doubleBytes, 0, output, offset, doubleBytes.Length); //Write double - this should take up 8 bytes
offset += doubleBytes.Length;

Buffer.BlockCopy(stringLengthBytes, 0, output, offset, stringLengthBytes.Length); //Write the length of the string so the decoder knows how much to decode - this should take up 4 bytes
offset += stringLengthBytes.Length;

Buffer.BlockCopy(stringBytes, 0, output, offset, stringBytes.Length); //Write string - this is variable, which is why we need to encode the length of the string above
offset += stringBytes.Length;

Buffer.BlockCopy(floatArrayLengthBytes, 0, output, offset, floatArrayLengthBytes.Length); //Write the length of the float array so the decoder knows how much to decode - this should take up 4 bytes
offset += floatArrayLengthBytes.Length;

Buffer.BlockCopy(floatArrayValues, 0, output, offset, Buffer.ByteLength(floatArrayValues)); //Write float array - this can be done directly without a byte array conversion because it's already an Array
offset += Buffer.ByteLength(floatArrayValues);

Debug.Log($"Encoded data in {output.Length} bytes");
return output;
}

/// <summary>
/// An example function which deserializes a pre-determined set of data described in the Serialize function above
/// </summary>
/// <param name="input">Input bytes - must be formatted in the expected manner by the Serialize function above</param>
/// <param name="intValue">Output int value deserialized from the data inside the input</param>
/// <param name="doubleValue">Output double value deserialized from the data inside the input</param>
/// <param name="stringValue">Output string value deserialized from the data inside the input</param>
/// <param name="floatArrayValues">Output float array value deserialized from the data inside the input</param>
/// <returns></returns>
bool Deserialize(byte[] input, out int intValue, out double doubleValue, out string stringValue, out float[] floatArrayValues)
{
int offset = 0;

intValue = BitConverter.ToInt32(input, offset);
offset += 4; //Increment the offset so the next item reads from the correct location. Ints should be 4 bytes long

doubleValue = BitConverter.ToDouble(input, offset);
offset += 8; //Doubles should be 8 bytes long

int stringLength = BitConverter.ToInt32(input, offset);
offset += 4; //String length is an int, which should be 4 bytes long

Debug.Log($"Decoding string length {stringLength} at offset {offset} for buffer length {input.Length}");
stringValue = Encoding.UTF8.GetString(input, offset, stringLength);
offset += stringLength; //Strings are variable length

int floatArrayByteLength = BitConverter.ToInt32(input, offset);
offset += 4; //Float array length is an int, which should be 4 bytes long

floatArrayValues = new float[floatArrayByteLength / 4]; //Create a new float array of the correct size to receive the data
Buffer.BlockCopy(input, offset, floatArrayValues, 0, floatArrayByteLength); //Copy the data from the input into the float array

return true;
}
}

最終更新: