Legacy Interfaces (Deprecated)

This guide provides reference documentation for the legacy interface system that was used in realvirtual before FastInterface was introduced. If you have existing interfaces built with these deprecated base classes, consider migrating to FastInterface for better performance, reliability, and maintainability.

Legacy Interface Base Classes

The legacy system provided two base classes for custom interface development:

InterfaceBaseClass (Single-threaded)

The simplest approach for basic protocol communication that runs on Unity's main thread:

using UnityEngine;

namespace realvirtual
{
    public class MyLegacyInterface : InterfaceBaseClass
    {
        [Header("Connection Settings")]
        public string ServerIP = "192.168.1.100";
        public int Port = 502;
        
        private bool isConnected = false;
        
        public override void OpenInterface()
        {
            try
            {
                // Connect to external system
                ConnectToServer(ServerIP, Port);
                isConnected = true;
                Debug.Log("Interface connected");
            }
            catch (System.Exception ex)
            {
                Debug.LogError($"Connection failed: {ex.Message}");
                isConnected = false;
            }
        }
        
        public override void CloseInterface()
        {
            try
            {
                DisconnectFromServer();
                isConnected = false;
                Debug.Log("Interface disconnected");
            }
            catch (System.Exception ex)
            {
                Debug.LogError($"Disconnect error: {ex.Message}");
            }
        }
        
        public override void CommunicationUpdate()
        {
            if (!isConnected) return;
            
            try
            {
                // Read Unity signals and send to external system
                var inputValues = ReadInputSignals();
                SendToExternalSystem(inputValues);
                
                // Read from external system and update Unity signals
                var receivedValues = ReceiveFromExternalSystem();
                WriteOutputSignals(receivedValues);
            }
            catch (System.Exception ex)
            {
                Debug.LogError($"Communication error: {ex.Message}");
                isConnected = false;
            }
        }
        
        public override bool GetCommunicationState()
        {
            return isConnected;
        }
    }
}

Key characteristics:

  • Runs on Unity's main thread

  • Simple synchronous operation

  • Can cause Unity frame drops with slow communication

  • Suitable only for very fast protocols or testing

InterfaceThreadedBaseClass (Multi-threaded)

A more advanced approach that runs communication on a background thread:

using UnityEngine;
using System.Threading;

namespace realvirtual
{
    public class MyThreadedInterface : InterfaceThreadedBaseClass
    {
        [Header("Connection Settings")]
        public string ServerIP = "192.168.1.100";
        public int Port = 502;
        
        private bool isConnected = false;
        private object connectionLock = new object();
        
        protected override void ThreadMethod()
        {
            while (!shouldStop)
            {
                try
                {
                    if (!isConnected)
                    {
                        AttemptConnection();
                    }
                    else
                    {
                        PerformCommunication();
                    }
                    
                    Thread.Sleep(UpdateCycleMs);
                }
                catch (System.Exception ex)
                {
                    Debug.LogError($"Thread error: {ex.Message}");
                    lock (connectionLock)
                    {
                        isConnected = false;
                    }
                    Thread.Sleep(1000); // Wait before retry
                }
            }
        }
        
        private void AttemptConnection()
        {
            try
            {
                ConnectToServer(ServerIP, Port);
                lock (connectionLock)
                {
                    isConnected = true;
                }
                Debug.Log("Interface connected");
            }
            catch (System.Exception ex)
            {
                Debug.LogError($"Connection failed: {ex.Message}");
            }
        }
        
        private void PerformCommunication()
        {
            // Read Unity signals (thread-safe)
            var inputValues = GetInputValuesThreadSafe();
            SendToExternalSystem(inputValues);
            
            // Read from external system and update Unity signals
            var receivedValues = ReceiveFromExternalSystem();
            SetOutputValuesThreadSafe(receivedValues);
        }
        
        public override bool GetCommunicationState()
        {
            lock (connectionLock)
            {
                return isConnected;
            }
        }
        
        protected override void OnDestroy()
        {
            CloseInterface();
            base.OnDestroy();
        }
    }
}

Key characteristics:

  • Runs on background thread

  • Better performance than single-threaded approach

  • Manual thread management required

  • Complex error handling and synchronization

  • Prone to threading issues and race conditions

Legacy Signal Management

The legacy system used different methods for signal access:

Reading Input Signals (Legacy)

// InterfaceBaseClass approach
private Dictionary<string, object> ReadInputSignals()
{
    var values = new Dictionary<string, object>();
    
    // Manual signal discovery and reading
    var signals = GetComponentsInChildren<Signal>();
    foreach (var signal in signals)
    {
        if (signal.Direction == SIGNALDIRECTION.INPUT)
        {
            values[signal.name] = signal.GetValue();
        }
    }
    
    return values;
}

// InterfaceThreadedBaseClass approach  
private Dictionary<string, object> GetInputValuesThreadSafe()
{
    var values = new Dictionary<string, object>();
    
    lock (signalLock)
    {
        foreach (var signal in inputSignals)
        {
            values[signal.Name] = signal.ThreadSafeValue;
        }
    }
    
    return values;
}

Writing Output Signals (Legacy)

// InterfaceBaseClass approach
private void WriteOutputSignals(Dictionary<string, object> values)
{
    var signals = GetComponentsInChildren<Signal>();
    foreach (var signal in signals)
    {
        if (signal.Direction == SIGNALDIRECTION.OUTPUT && values.ContainsKey(signal.name))
        {
            signal.SetValue(values[signal.name]);
        }
    }
}

// InterfaceThreadedBaseClass approach
private void SetOutputValuesThreadSafe(Dictionary<string, object> values)
{
    lock (signalLock)
    {
        foreach (var kvp in values)
        {
            if (outputSignals.ContainsKey(kvp.Key))
            {
                outputSignals[kvp.Key].ThreadSafeValue = kvp.Value;
            }
        }
    }
}

Common Issues with Legacy System

Threading Problems

The legacy threaded approach was prone to various threading issues:

// PROBLEMATIC: Direct Unity API access from background thread
protected override void ThreadMethod()
{
    while (!shouldStop)
    {
        // This causes errors - Unity APIs not thread-safe
        Debug.Log("This will crash!"); 
        transform.position = Vector3.zero; // This will crash!
        
        Thread.Sleep(UpdateCycleMs);
    }
}

Manual Signal Management

Legacy interfaces required manual signal discovery and management:

private List<Signal> inputSignals = new List<Signal>();
private List<Signal> outputSignals = new List<Signal>();

private void DiscoverSignals()
{
    // Manual signal discovery - error prone
    var allSignals = GetComponentsInChildren<Signal>();
    foreach (var signal in allSignals)
    {
        if (signal.Direction == SIGNALDIRECTION.INPUT)
            inputSignals.Add(signal);
        else if (signal.Direction == SIGNALDIRECTION.OUTPUT)
            outputSignals.Add(signal);
    }
}

No Automatic Reconnection

Legacy interfaces required manual reconnection logic:

protected override void ThreadMethod()
{
    while (!shouldStop)
    {
        try
        {
            if (!isConnected)
            {
                // Manual reconnection attempts
                reconnectAttempts++;
                if (reconnectAttempts > maxReconnectAttempts)
                {
                    Thread.Sleep(5000); // Wait longer before retry
                    reconnectAttempts = 0;
                }
                AttemptConnection();
            }
            else
            {
                PerformCommunication();
                reconnectAttempts = 0; // Reset on successful communication
            }
        }
        catch (Exception ex)
        {
            Debug.LogError($"Communication error: {ex.Message}");
            isConnected = false;
        }
        
        Thread.Sleep(UpdateCycleMs);
    }
}

Migration Guide to FastInterface

To migrate from legacy interfaces to FastInterface, follow these key steps:

1. Change Base Class

// OLD: Legacy approach
public class MyInterface : InterfaceThreadedBaseClass
{
    // Legacy implementation
}

// NEW: FastInterface approach
public class MyInterface : FastInterfaceBase
{
    // FastInterface implementation
}

2. Replace Thread Management

// OLD: Manual thread management
protected override void ThreadMethod()
{
    while (!shouldStop)
    {
        try
        {
            if (!isConnected)
                AttemptConnection();
            else
                PerformCommunication();
            
            Thread.Sleep(UpdateCycleMs);
        }
        catch (Exception ex)
        {
            // Manual error handling
        }
    }
}

// NEW: FastInterface methods
protected override async Task EstablishConnection(CancellationToken cancellationToken)
{
    // Connection logic here
}

protected override async Task CommunicationLoop(CancellationToken cancellationToken)
{
    // Communication logic here
}

protected override void CloseConnection()
{
    // Cleanup logic here
}

3. Use Built-in Signal Management

// OLD: Manual signal handling
private void ReadInputSignals()
{
    var signals = GetComponentsInChildren<Signal>();
    foreach (var signal in signals)
    {
        if (signal.Direction == SIGNALDIRECTION.INPUT)
        {
            var value = signal.GetValue();
            // Send to external system
        }
    }
}

// NEW: FastInterface signal management
protected override async Task CommunicationLoop(CancellationToken cancellationToken)
{
    // Automatic signal discovery and management
    var inputs = GetInputsForPLC();
    await SendToExternalSystem(inputs);
    
    var receivedData = await ReceiveFromExternalSystem();
    SetOutputsFromPLC(receivedData);
}

4. Update Logging

// OLD: Unity Debug.Log (causes threading issues)
Debug.Log("Connection established");
Debug.LogError($"Error: {ex.Message}");

// NEW: Thread-safe logging
ThreadSafeLogger.LogInfo("Connection established", GetType().Name);
ThreadSafeLogger.LogError($"Error: {ex.Message}", GetType().Name);

5. Handle Properties Thread-Safely

// OLD: Direct property access (threading issues)
protected override void ThreadMethod()
{
    // Direct access to Inspector properties - not thread safe
    ConnectToServer(ServerIP, Port);
}

// NEW: Thread-safe property copying
public string ServerIP = "192.168.1.100";
private string threadSafeServerIP;

protected override void CopyPropertiesToThreadSafe()
{
    threadSafeServerIP = ServerIP;
}

protected override async Task EstablishConnection(CancellationToken cancellationToken)
{
    // Use thread-safe copy
    await ConnectToServer(threadSafeServerIP, threadSafePort);
}

Legacy Interface Examples

Here's a complete legacy interface example for reference:

using UnityEngine;
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Threading;
using NaughtyAttributes;

namespace realvirtual
{
    public class LegacyTCPInterface : InterfaceThreadedBaseClass
    {
        [Header("TCP Settings")]
        public string ServerIP = "192.168.1.100";
        public int Port = 8080;
        public int TimeoutMs = 2000;
        
        private TcpClient tcpClient;
        private NetworkStream stream;
        private bool isConnected = false;
        private object connectionLock = new object();
        private List<Signal> inputSignals = new List<Signal>();
        private List<Signal> outputSignals = new List<Signal>();
        
        protected override void Start()
        {
            base.Start();
            DiscoverSignals();
        }
        
        private void DiscoverSignals()
        {
            var allSignals = GetComponentsInChildren<Signal>();
            foreach (var signal in allSignals)
            {
                if (signal.Direction == SIGNALDIRECTION.INPUT)
                    inputSignals.Add(signal);
                else if (signal.Direction == SIGNALDIRECTION.OUTPUT)
                    outputSignals.Add(signal);
            }
            
            Debug.Log($"Discovered {inputSignals.Count} inputs, {outputSignals.Count} outputs");
        }
        
        protected override void ThreadMethod()
        {
            int reconnectAttempts = 0;
            const int maxReconnectAttempts = 5;
            
            while (!shouldStop)
            {
                try
                {
                    lock (connectionLock)
                    {
                        if (!isConnected)
                        {
                            AttemptConnection();
                            if (!isConnected)
                            {
                                reconnectAttempts++;
                                if (reconnectAttempts >= maxReconnectAttempts)
                                {
                                    Thread.Sleep(5000);
                                    reconnectAttempts = 0;
                                }
                                continue;
                            }
                        }
                    }
                    
                    PerformCommunication();
                    reconnectAttempts = 0;
                }
                catch (Exception ex)
                {
                    Debug.LogError($"Communication error: {ex.Message}");
                    lock (connectionLock)
                    {
                        isConnected = false;
                        CleanupConnection();
                    }
                }
                
                Thread.Sleep(UpdateCycleMs);
            }
        }
        
        private void AttemptConnection()
        {
            try
            {
                tcpClient = new TcpClient();
                tcpClient.ConnectTimeout = TimeoutMs;
                tcpClient.Connect(ServerIP, Port);
                stream = tcpClient.GetStream();
                isConnected = true;
                Debug.Log("TCP connection established");
            }
            catch (Exception ex)
            {
                Debug.LogError($"Connection failed: {ex.Message}");
                CleanupConnection();
            }
        }
        
        private void PerformCommunication()
        {
            // Read Unity input signals
            var inputData = new Dictionary<string, object>();
            foreach (var signal in inputSignals)
            {
                inputData[signal.name] = signal.GetValue();
            }
            
            // Send to external system
            if (inputData.Count > 0)
            {
                SendDataToServer(inputData);
            }
            
            // Request data from external system
            var receivedData = RequestDataFromServer();
            
            // Update Unity output signals
            foreach (var kvp in receivedData)
            {
                var signal = outputSignals.Find(s => s.name == kvp.Key);
                if (signal != null)
                {
                    signal.SetValue(kvp.Value);
                }
            }
        }
        
        private void SendDataToServer(Dictionary<string, object> data)
        {
            // Implementation depends on your protocol
        }
        
        private Dictionary<string, object> RequestDataFromServer()
        {
            // Implementation depends on your protocol
            return new Dictionary<string, object>();
        }
        
        private void CleanupConnection()
        {
            try
            {
                stream?.Close();
                tcpClient?.Close();
            }
            catch (Exception ex)
            {
                Debug.LogError($"Cleanup error: {ex.Message}");
            }
            finally
            {
                stream = null;
                tcpClient = null;
                isConnected = false;
            }
        }
        
        public override bool GetCommunicationState()
        {
            lock (connectionLock)
            {
                return isConnected;
            }
        }
        
        protected override void OnDestroy()
        {
            shouldStop = true;
            
            lock (connectionLock)
            {
                CleanupConnection();
            }
            
            base.OnDestroy();
        }
    }
}

Why FastInterface is Better

FastInterface addresses all the major problems of the legacy system:

Legacy System Issues
FastInterface Solutions

Manual thread management

Automatic thread lifecycle management

Complex error handling

Built-in reconnection and error recovery

Threading bugs and race conditions

Thread-safe design patterns

Manual signal discovery

Automatic signal detection and management

No performance optimization

Built-in change detection and batching

Inconsistent implementations

Standardized architecture across all interfaces

Limited debugging tools

Comprehensive logging and state monitoring

Property synchronization issues

Automatic thread-safe property copying

See Also

Last updated