stella3d / OscCore

A performance-oriented OSC library for Unity
MIT License
159 stars 28 forks source link

[TODO] - Struct Read/Write #9

Open stella3d opened 2 years ago

stella3d commented 2 years ago

Basics

What if we want to transmit messages that involve multiple different OSC element types at once?

We'd want to model that as a struct, for example:

struct StatusMessage {
  public int Action;
  public float Energy;

  public StatusMessage(int enumVal, float energy) { Action = enumVal; Energy = energy; }
}

Writing

Ideal API

var writer = new OscWriter();
var msg = new StatusMessage(1, 0.4f);
// the generic type should be implied in real code - this is to show it's generic 
writer.Write<StatusMessage>(msg);

this OscWriter.Write<T> should work for any struct T that contains only:

It would be nice if we could just constrain this T like where T: unmanaged, but lots of structs that would be valid OSC messages don't meet the unmanaged constraint. Instead, i think we will have to rely on runtime errors. I know how to do automated type-checking of projects via reflection in the Unity editor, but it's more hassle and code than i think is worth for this.

Implementation Details

As long as all fields of the struct map directly to an OSC primitive, then this is trivial, as OSC message have multiple fields.

struct OurOscMessage {
  int NumberOf;
  float DegreeOf;
}

becomes an OSC message with two fields: int then float.

oscWriter.Write(intValue);
oscWriter.Write(floatValue);

this works recursively - as long as all contained structs' fields map directly to OSC primitives, trivial to do.

struct ComplexOscMessage {
  int NumberOf;
  float DegreeOf;
  Vector3 Position;
}

becomes an OSC message with 5 elements:

oscWriter.Write(intValue);
oscWriter.Write(floatValue);
// next 3 are the Vector3 struct fields
oscWriter.Write(xFloat);      
oscWriter.Write(yFloat);
oscWriter.Write(zFloat);

Array support

Arrays of primitives should be trivial.

struct IntBufferMessage {
  int[] Data;
}

Serialization is fairly simple if the other size knows how to interpret the message: 1 OSC field of the proper type for each element in the array

foreach (int element in intBufferMsg)
{
  oscWriter.Write(element); 
}

However we may need to add Array type tags for some cases, where the receiver doesn't have the type definition of a message being received. OscCore doesn't really support this yet

// this runs during type tags writing step
const msgData = new int[16];    // assume this is real data not zeros
const intElemTags = new String("i", msgData.Length);
const string intArrTypeTags = $"[{intElemTags}]";

// actual array value serialization functions just as before
foreach (int element in intBufferMsg)
  { oscWriter.Write(element); }

array support should also mean that fixed-size buffers in unsafe structs work.

Arrays of structs

struct Element {
  public int Number;
  public Vector3 Position;
}

struct Message {
  public Element[] Data;
}

serializes like:

const bufferMsg = new Element[16];   // fill with real data

// this runs during type tags writing step
// repeat the Element struct's type tags ("ifff", int then 3 floats for Vector3)
const intElemTags = new String("ifff", msgData.Length);
const string intArrTypeTags = $"[{intElemTags}]";

// actual array value serialization functions just as before
foreach (int element in bufferMsg)
{ 
  oscWriter.Write(element.Number);
  oscWriter.Write(element.Position);
}