s&box has a [CodeGenerator]
attribute that you can use to decorate another attribute specifically for use with methods and properties. It lets you wrap methods and properties to perform some other action when the method is called or the property is set or to return a different value when the property is read.
The Scene System uses CodeGen for Broadcast RPCs, it could also be used for creating networked variables.
The following is an example of how you could use CodeGenerator
to create an RPC system.
[CodeGenerator( CodeGeneratorFlags.WrapMethod | CodeGeneratorFlags.Instance, "OnRPCInvoked" )]
public class RPC : Attribute {}
public class MyObject
{
[RPC]
public void SendMessage( string message )
{
Log.Info( message );
}
internal void OnRPCInvoked( WrappedMethod m, params object[] args )
{
if ( IsServer )
{
// Send a networked message with the specified args and method name to all clients.
}
// Call the original method.
m.Resume();
}
}
This is an example of how you could use CodeGenerator
to create a system for networked variables.
[CodeGenerator( CodeGeneratorFlags.WrapPropertySet | CodeGeneratorFlags.Instance, "OnNetVarSet" )]
[CodeGenerator( CodeGeneratorFlags.WrapPropertyGet | CodeGeneratorFlags.Instance, "OnNetVarGet" )]
public class NetVar : Attribute {}
public class MyObject
{
[NetVar] public string Name { get; set; }
internal T OnWrapGet<T>( WrappedPropertyGet<T> p)
{
// Return the actual value from the network.
if ( MyNetVarTable.TryGetValue( p.PropertyName, out var netValue ) )
{
return (T)netValue;
}
return val;
}
internal void OnWrapSet<T>( WrappedPropertySet<T> p )
{
if ( IsServer )
{
MyNetVarTable[propertyName] = p.Value;
// Send a networked message setting the property to this value for all clients.
}
p.Setter( p.Value);
}
}
The CodeGenerator
attribute needs to have CodeGeneratorFlags
set which determine what it will wrap and whether the wrapping applies to static methods and properties or instance methods and properties or both.
nAn attribute class can be decorated with more than one CodeGenerator
attribute to handle many different scenarios.
To simply wrap a method call you can create an attribute that is decorated with CodeGenerator
and the CodeGeneratorFlags.WrapMethod
flag. To create one that wraps both instance and static methods you can use something like this:
[AttributeUsage( AttributeTargets.Method )]
[CodeGenerator( CodeGeneratorFlags.WrapMethod | CodeGeneratorFlags.Instance, "OnMethodInvoked" )]
[CodeGenerator( CodeGeneratorFlags.WrapMethod | CodeGeneratorFlags.Static,
"MyObject.OnMethodInvokedStatic" )]
public class WrapCall : Attribute {}
The passed callbackName
to the CodeGenerator
attribute can either be a static method (determined by whether there's a .
in the string) or an instance method. You can only use an instance method if the wrapped method itself is an instance method.
You can then setup those target methods on your object or static class like this:
public class MyObject
{
internal static void OnMethodInvokedStatic( WrappedMethod m, params object[] args ) {}
internal void OnMethodInvoked( WrappedMethod m, params object[] args ) {}
}
methodName
on a static callback will be the fully qualified name. For example if [WrapCall]
was added to a method called DoSomething
on MyClass
then the method name would be MyClass.DoSomething
.
You can handle different parameter types instead of having a single generic callback signature. The correct method will be called based on the original parameters of the wrapped method. You can even use generics here.
public class MyObject
{
internal static void OnMethodInvokedStatic<T1, T2>( WrappedMethod m, T1 arg1, T2 arg2 ) {}
internal void OnMethodInvoked( WrappedMethod m, bool enabled ) {}
internal void OnMethodInvoked<T1, T2, T3>( WrappedMethod m, T1 arg1, T2 arg3, T3 arg3 ) {}
}
If you want to handle specific return types you can also do that. The crucial part is that instead of the first parameter of the callback method being an Action
it would be a Func<T>
instead.
public class MyObject
{
internal T OnMethodInvoked( WrappedMethod<T> m )
{
return m.Resume();
}
}
Wrapping properties is similar to wrapping a method, but your attribute class should use CodeGeneratorFlags.WrapPropertySet
and/or CodeGeneratorFlags.WrapPropertyGet
.
[AttributeUsage( AttributeTargets.Property )]
[CodeGenerator( CodeGeneratorFlags.WrapPropertySet | CodeGeneratorFlags.Instance, "OnWrapSet" )]
[CodeGenerator( CodeGeneratorFlags.WrapPropertyGet | CodeGeneratorFlags.Instance, "OnWrapGet" )]
public class WrapGetSet : Attribute {}
Similarly to wrapping methods, the callback method can handle any generic property or specific property types.
When wrapping the setter of properties the callback method should have 3 parameters. The first is the property name, the second is the value that the property wants to be set to, and the third is an Action
that will call the original setter function.
public void OnWrapSet<T>( WrappedPropertySet<T> p )
{
p.Setter( p.Value );
}
When wrapping the getter of properties, the callback method should have a return type and 2 parameters. The first being the property name and the second being the value that the getter would have returned usually.
public T OnWrapGet<T>( WrappedPropertyGet<T> p )
{
return p.Value;
}
To demonstrate how you can mix CodeGeneratorFlags
to handle multiple use cases, here is an example of an attribute that could wrap anything and everything.
Because we specify CodeGeneratorFlags.Static
in this attribute, the callbackName
must refer to a static method, too.
[CodeGenerator(
CodeGeneratorFlags.WrapPropertySet | CodeGeneratorFlags.WrapPropertyGet | CodeGeneratorFlags.WrapMethod |
CodeGeneratorFlags.Static | CodeGeneratorFlags.Instance, "MyStaticClass.OnWrapAnything" )]
public class WrapAnything : Attribute {}
public class MyObject
{
[WrapAnything] public string MyString { get; set; }
[WrapAnything] public static string MyStaticString { get; set; }
[WrapAnything]
public void MyMethod()
{
}
[WrapAnything]
public static void MyStaticMethod()
{
}
}
public static class MyStaticClass
{
internal static void OnWrapAnything<T>( WrappedPropertySet<T> p )
{
}
internal static T OnWrapAnything<T>( WrappedPropertyGet<T> p )
{
return value;
}
internal static void OnWrapAnything( WrappedMethod m, params object[] args )
{
m.Resume();
}
internal static T OnWrapAnything( WrappedMethod<T> p, params object[] args )
{
m.Resume();
}
}