Skip to content

[Change] callback on [Sync] property throws when declared on a generic Component base class #11352

Description

@Remscar

Version / Commit

26.06.24

Describe the bug

Applying [Change] to a [Sync] property on a generic Component subclass throws System.InvalidOperationException: Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true

S&box resolves the change callback via reflection on the open generic type definition (PredictionController<TInput, TState>) instead of the closed constructed type on the instance (e.g. ShrimplePredictionController => PredictionController<ShrimplePlayerInput, ShrimplePlayerState>). Reflection cannot invoke instance methods on types that still contain generic parameters.

Exception when setting Prediction.PredictionController<TInput, TState>.ControllerId - Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.
System.InvalidOperationException: Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.
   at System.Reflection.RuntimeMethodInfo.ThrowNoInvokeException()
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Sandbox.PropertyDescription.GetValue(Object obj)
   at Sandbox.ConsoleSystem.OnChangePropertySet[T](WrappedPropertySet`1& p)
   at Prediction.PredictionController`2.<set_ControllerId>b__36_0(Guid v) in F:\sbox\networkingtest\Code\Prediction\PredictionController.cs:line 83
   at Sandbox.Component.__sync_SetValue[T](WrappedPropertySet`1& p)

To Reproduce

public abstract class PredictionController<TInput, TState> : Component
    where TInput : struct
    where TState : struct
{
    [Property, Sync( SyncFlags.FromHost ), Change( nameof( OnControllerIdChanged ) )]
    private Guid ControllerId { get; set; }
    private void OnControllerIdChanged( Guid oldValue, Guid newValue )
    {
        // never reached
    }
}
public sealed class ShrimplePredictionController
    : PredictionController<ShrimplePlayerInput, ShrimplePlayerState> { }

Expected behavior

[Change] callbacks on synced properties should work on generic Component subclasses.

Media/Files

No response

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions