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
Section titled “Legacy Interface Base Classes”The legacy system provided two base classes for custom interface development:
InterfaceBaseClass (Single-threaded)
Section titled “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)
Section titled “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
Section titled “Legacy Signal Management”The legacy system used different methods for signal access:
Reading Input Signals (Legacy)
Section titled “Reading Input Signals (Legacy)”// InterfaceBaseClass approachprivate 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 approachprivate 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)
Section titled “Writing Output Signals (Legacy)”// InterfaceBaseClass approachprivate 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 approachprivate 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
Section titled “Common Issues with Legacy System”Threading Problems
Section titled “Threading Problems”The legacy threaded approach was prone to various threading issues:
// PROBLEMATIC: Direct Unity API access from background threadprotected 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
Section titled “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
Section titled “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
Section titled “Migration Guide to FastInterface”To migrate from legacy interfaces to FastInterface, follow these key steps:
1. Change Base Class
Section titled “1. Change Base Class”// OLD: Legacy approachpublic class MyInterface : InterfaceThreadedBaseClass{ // Legacy implementation}
// NEW: FastInterface approachpublic class MyInterface : FastInterfaceBase{ // FastInterface implementation}2. Replace Thread Management
Section titled “2. Replace Thread Management”// OLD: Manual thread managementprotected override void ThreadMethod(){ while (!shouldStop) { try { if (!isConnected) AttemptConnection(); else PerformCommunication();
Thread.Sleep(UpdateCycleMs); } catch (Exception ex) { // Manual error handling } }}
// NEW: FastInterface methodsprotected 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
Section titled “3. Use Built-in Signal Management”// OLD: Manual signal handlingprivate 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 managementprotected 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
Section titled “4. Update Logging”// OLD: Unity Debug.Log (causes threading issues)Debug.Log("Connection established");Debug.LogError($"Error: {ex.Message}");
// NEW: Thread-safe loggingThreadSafeLogger.LogInfo("Connection established", GetType().Name);ThreadSafeLogger.LogError($"Error: {ex.Message}", GetType().Name);5. Handle Properties Thread-Safely
Section titled “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 copyingpublic 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
Section titled “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
Section titled “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
Section titled “See Also”- Building Custom Interfaces - Modern FastInterface development guide
- Interface Overview - Overview of all available interfaces
- Signal Management - Understanding realvirtual signals
- S7 Interface - Example of FastInterface implementation