LogoLogo
HomeOnline DashboardAPIDiscordForums
SDK 0.5.2
SDK 0.5.2
  • Welcome
  • Overview
    • What is coherence?
    • How does coherence work?
    • Features and Roadmap
    • Requirements
  • Get Started
    • Install coherence
    • Scene setup
    • Prefab setup
    • Build and run
    • Baking and code generation
    • Create a free account
    • Deploy replication server
    • Share builds
  • Authority and communication
    • How authority works
    • Authority transfer
    • Commands
    • Client messages
    • Server-side and input queues
    • Animations
  • Persistence
    • Overview
    • Configuring persistence
    • Storage
    • Example – A global counter
  • Optimization
    • Overview
    • Simulation frequency
    • Areas of interest
    • World size
    • Level of detail
    • Interpolation
    • Extrapolation
  • Connected entities
    • Overview
    • Entity references
    • Parent-child relationships
  • Simulators
    • Overview
    • Client vs. simulator logic
    • Build and deploy
    • Simulator load balancing
  • Tutorial project
    • Get the Tutorial Project
    • Start Tutorial
      • 1. Transforms
      • 2. Physics
      • 3. Persistence
      • 4. Animation and Variables
      • 5. AI Navigation
      • 6. Network Commands
      • 7. Network Teams (draft)
  • Game Services
    • Game account
    • Key-value store
    • Matchmaking
  • Developer Portal
    • Overview
    • Dashboard
    • Resource Usage
    • Replicator and Simulator Configuration
    • Enabling Game Services
  • API reference
    • Network SDK
      • CoherenceSync
      • MonoBridge
      • LiveQuery
      • Archetype
      • Sample UI
      • Settings Window
      • Custom Bindings
    • Cloud API
      • API tokens and keys
      • Server discovery
      • Game account
      • Key-value store
      • Matchmaking
    • Replication Server
    • Simulation Server
  • Schema reference
    • Overview
    • Specification
    • Field Settings
    • Archetypes and LOD-ing
  • Resources
    • Downloads
    • SDK Update Guide
    • Video Tutorials
    • Glossary
    • CLI Utilities
    • Helper Scripts
    • Troubleshooting
  • Community
    • Discord
  • Additional information
    • Pricing
    • SLA
    • Unreal Engine support
    • WebGL
    • Peer-to-Peer (P2P)
    • Known Issues
    • Changelog
Powered by GitBook
On this page
  • CustomBindingRuntimeSerializer
  • Extending and inheriting CustomBindingProviders

Was this helpful?

Export as PDF
  1. API reference
  2. Network SDK

Custom Bindings

This is an advanced topic that aims to bring access to coherence's internals to the end user.

CustomBindingProviders are editor classes that tell coherence how a specific component should expose its bindings and how it generates baked scripts.

For example, we could create a custom binding provider for our Health component:

using UnityEngine;

public class Health : MonoBehaviour
{
    public int health;
}
using UnityEngine;
using Coherence.Toolkit;

[CustomBindingProvider(typeof(Health))]
public class HealthBindingProvider : CustomBindingProvider
{
    // check the CustomBindingProvider base class to
    // explore the possibilities!
}

Place CustomBindingProviders inside an Editor folder.

We can add additional (custom) bindings:

using System.Collections.Generic;
using UnityEngine;
using Coherence.Toolkit;

[CustomBindingProvider(typeof(Health))]
public class CustomTransformBindingProvider : CustomBindingProvider
{
    public override void FetchDescriptors(Component component, List<CustomBinding.Descriptor> descriptors)
    {
        base.FetchDescriptors(component, descriptors);
        descriptors.Add(new CustomBinding.Descriptor("myCustomBinding", typeof(int))
        {
            bakedSyncScriptGetter = "myCustomBindingGet",
            bakedSyncScriptSetter = "myCustomBindingSet",
            coherenceComponentName = "MyCustomBinding",
            coherenceComponentFieldName = "value",
            schemaComponentFieldName = "value",
        });
        descriptors.Add(new CustomBinding.Descriptor("debugBinding", typeof(bool))
        {
            bakedSyncScriptGetter = "debugBindingGet",
            bakedSyncScriptSetter = "debugBindingSet",
            coherenceComponentName = "DebugBinding",
            coherenceComponentFieldName = "value",
            schemaComponentFieldName = "value",
        });
    }
}

In order for these new custom bindings to work on reflected mode, we'll need to implement a runtime serializer that understands how to deal with them.

Check the CustomBinding.Descriptor API for further properties, like interpolation or marking the binding as required.

CustomBindingRuntimeSerializer

For custom bindings to work on reflected mode, we need to implement how their values are serialized and deserialized at runtime:

using UnityEngine;
using Coherence.Toolkit;
using Coherence.Entity;
using Coherence.SimulationFrame;

// this class defines how reflected mode serializes and deserializes Health's custom bindings

public class HealthRuntimeSerializer : CustomBindingRuntimeSerializer
{
    public override void Serialize(CustomBinding customBinding, ICoherenceComponentData componentData)
    {
        var health = customBinding.component as Health;

        if (customBinding.descriptor.name == "debugBinding")
        {
            Debug.Log("[HEALTH] Serializing debugBinding");
        }
        else if (customBinding.descriptor.name == "myCustomBinding")
        {
            // in this example, lets send myCustomBinding as "health" over the network
            var fi = componentData.GetType().GetField("value");
            fi.SetValue(componentData, health.health);
        }
    }

    public override void Deserialize(CoherenceMonoBridge monoBridge, CustomBinding customBinding, ICoherenceComponentData componentData)
    {
        var health = customBinding.component as Health;

        if (customBinding.descriptor.name == "debugBinding")
        {
            Debug.Log("[HEALTH] Deserializing debugBinding");
        }
        else if (customBinding.descriptor.name == "myCustomBinding")
        {
            var fi = componentData.GetType().GetField("value");
            var v = (float)fi.GetValue(componentData);
            // we take the value of myCustomBinding, which we know we serialized with the valeu of health
            // in this example, we just do some custom logic: updating the isAlive bool with it
            health.isAlive = v > 0;
        }
    }

    public override void PerformInterpolation(CoherenceMonoBridge monoBridge, CustomBinding customBinding, AbsoluteSimulationFrame localFrame, float deltaTime)
    {
    }

    public override void ChangeInterpolationSetup(CustomBinding customBinding, string setupName)
    {
    }

    public override void ResetInterpolation(CustomBinding customBinding)
    {
    }
}

CustomBindingRuntimeSerializers should be placed in a non-Editor folder.

Once we have our runtime serializer, we need to make our binding provider use it:

using System.Collections.Generic;
using UnityEngine;
using Coherence.Toolkit;

[CustomBindingProvider(typeof(Health))]
public class CustomTransformBindingProvider : CustomBindingProvider
{
    // here
    public override CustomBindingRuntimeSerializer CustomBindingRuntimeSerializer => new HealthRuntimeSerializer();

    public override void FetchDescriptors(Component component, List<CustomBinding.Descriptor> descriptors)
    {
        base.FetchDescriptors(component, descriptors);
        descriptors.Add(new CustomBinding.Descriptor("myCustomBinding", typeof(int))
        {
            bakedSyncScriptGetter = "myCustomBindingGet",
            bakedSyncScriptSetter = "myCustomBindingSet",
            coherenceComponentName = "MyCustomBinding",
            coherenceComponentFieldName = "value",
            schemaComponentFieldName = "value",
        });
        descriptors.Add(new CustomBinding.Descriptor("debugBinding", typeof(bool))
        {
            bakedSyncScriptGetter = "debugBindingGet",
            bakedSyncScriptSetter = "debugBindingSet",
            coherenceComponentName = "DebugBinding",
            coherenceComponentFieldName = "value",
            schemaComponentFieldName = "value",
        });
    }
}

Extending and inheriting CustomBindingProviders

You can extend an already existing CustomBindingProvider. For example, coherence ships with a CustomBindingProvider for Transforms:

using System.Collections.Generic;
using UnityEngine;
using Coherence.Toolkit;

[CustomBindingProvider(typeof(Transform))]
public class CustomTransformBindingProvider : TransformBindingProvider
{
    public override void FetchDescriptors(Component component, List<CustomBinding.Descriptor> descriptors)
    {
        base.FetchDescriptors(component, descriptors);
        // your changes
    }
    
    // ...
}

This way, you can define additional rules on how you want to treat your Transforms, for example.

Any amount of CustomBindingProviders can be registered over the same component, but only one being used. The one being used, is resolved by a priority integer that you can specify in the CustomBindingProvider attribute. The class with the higher priority defined in the attribute will be the only provider taken into account:

[CustomBindingProvider(typeof(Transform), 1)]

The default priority is set at 0, and coherence's internal CustomBindingProviders have a priority of -1.

To understand how these APIs are used, check out TransformBindingProvider and AnimatorBindingProvider, both shipped with the coherence SDK (<package>/Coherence.Editor/Toolkit/CustomBindingProviders).

Last updated 3 years ago

Was this helpful?