Skip to content

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.

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

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

The legacy system used different methods for signal access:

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

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

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

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

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

// OLD: Legacy approach
public class MyInterface : InterfaceThreadedBaseClass
{
// Legacy implementation
}
// NEW: FastInterface approach
public class MyInterface : FastInterfaceBase
{
// FastInterface implementation
}
// 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
}
// 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);
}
// 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);
// 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);
}

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

FastInterface addresses all the major problems of the legacy system:

Legacy System IssuesFastInterface Solutions
Manual thread managementAutomatic thread lifecycle management
Complex error handlingBuilt-in reconnection and error recovery
Threading bugs and race conditionsThread-safe design patterns
Manual signal discoveryAutomatic signal detection and management
No performance optimizationBuilt-in change detection and batching
Inconsistent implementationsStandardized architecture across all interfaces
Limited debugging toolsComprehensive logging and state monitoring
Property synchronization issuesAutomatic thread-safe property copying