Legacy Interfaces (Deprecated)
Deprecated: This documentation covers the legacy interface system using InterfaceBaseClass
and InterfaceThreadedBaseClass
. These approaches are deprecated and should not be used for new projects. Use FastInterface for all new interface development.
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:
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
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
Last updated