Capabilities

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.

Examples

The Scene System uses CodeGen for Broadcast RPCs, it could also be used for creating networked variables.

RPCs

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();
  }
}

Networked Vars

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);
  }
}

Code Generator Flags

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.

Wrapping Methods

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.

Different Parameter Types

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 ) {}
}

Different Return Types

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

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;
}

Wrapping Everything

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();
  }
}





Created 19 Nov 2023
Updated 15 Jun 2025