Co-locate the two cockpit-pod projects into a single repository:
- Console/ : TeslaConsole, the net48 WinForms operator console (decompiled
reconstruction) plus its differential + catalog test suite.
- Launcher/ : TeslaLauncher, the net6 pod-side Service + Agent rewrite.
Adds a combined TeslaSuite.sln, root README documenting the shared wire
contract (and its current duplication, the main follow-up), and a root
.gitignore. Histories were not preserved per request; this is a fresh start
from the current working state of both projects.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
365 lines
5.8 KiB
C#
365 lines
5.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Windows.Forms;
|
|
using Munga.Net;
|
|
|
|
namespace TeslaConsole;
|
|
|
|
public class MungaGame
|
|
{
|
|
private readonly object mInternalLock = new object();
|
|
|
|
private readonly Pod mPod;
|
|
|
|
private Image mImage;
|
|
|
|
private string mStatusMessage;
|
|
|
|
private MungaSocket mSocket;
|
|
|
|
private DateTime mNextStateQuery = DateTime.MinValue;
|
|
|
|
private ApplicationState? mAppState;
|
|
|
|
private ApplicationID? mAppID;
|
|
|
|
private DateTime mEggDelayTimer = DateTime.MinValue;
|
|
|
|
private DateTime mEggSent = DateTime.MinValue;
|
|
|
|
private bool mEggAck;
|
|
|
|
private MungaConnectionState mState;
|
|
|
|
private readonly List<object> mRequestors = new List<object>();
|
|
|
|
private Form mOwner;
|
|
|
|
public Image Image => mImage;
|
|
|
|
public string StatusMessage => mStatusMessage;
|
|
|
|
public Pod Pod => mPod;
|
|
|
|
public ApplicationState? AppState => mAppState;
|
|
|
|
public ApplicationID? AppID => mAppID;
|
|
|
|
public DateTime EggDelayTimer
|
|
{
|
|
get
|
|
{
|
|
return mEggDelayTimer;
|
|
}
|
|
set
|
|
{
|
|
mEggDelayTimer = value;
|
|
}
|
|
}
|
|
|
|
public DateTime EggSent
|
|
{
|
|
get
|
|
{
|
|
return mEggSent;
|
|
}
|
|
set
|
|
{
|
|
mEggSent = value;
|
|
}
|
|
}
|
|
|
|
public bool EggAcknowledged => mEggAck;
|
|
|
|
public MungaConnectionState State
|
|
{
|
|
get
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
if (mState == MungaConnectionState.Connected)
|
|
{
|
|
if (mSocket != null && mSocket.Connected)
|
|
{
|
|
return MungaConnectionState.Connected;
|
|
}
|
|
Disconnect();
|
|
}
|
|
return mState;
|
|
}
|
|
}
|
|
private set
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
mState = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool Requested
|
|
{
|
|
get
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
return mRequestors.Count > 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsOwned
|
|
{
|
|
get
|
|
{
|
|
if (mOwner != null && mOwner.IsDisposed)
|
|
{
|
|
mOwner = null;
|
|
}
|
|
return mOwner != null;
|
|
}
|
|
}
|
|
|
|
public event EventHandler OnPodStatusUpdated;
|
|
|
|
public void QueryStateIfNeeded()
|
|
{
|
|
DateTime now = DateTime.Now;
|
|
if (mNextStateQuery < now)
|
|
{
|
|
Send(0, 1, new StateQueryMessage(1));
|
|
mNextStateQuery = now.AddSeconds(1.0);
|
|
}
|
|
}
|
|
|
|
public bool IsPodAppState(ApplicationID app, ApplicationState state)
|
|
{
|
|
return IsPodAppState(app, new ApplicationState[1] { state });
|
|
}
|
|
|
|
public bool IsPodAppState(ApplicationID app, IEnumerable<ApplicationState> states)
|
|
{
|
|
if (IsPodOperational(app))
|
|
{
|
|
foreach (ApplicationState state in states)
|
|
{
|
|
if (mAppState.Value == state)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool IsPodOperational(ApplicationID app)
|
|
{
|
|
if (State == MungaConnectionState.Connected && mAppID.HasValue && mAppID.Value == app)
|
|
{
|
|
return mAppState.HasValue;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void MakeRequested(object requestor)
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
if (!mRequestors.Contains(requestor))
|
|
{
|
|
mRequestors.Add(requestor);
|
|
if (mRequestors.Count == 1)
|
|
{
|
|
Connect();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ReleaseRequest(object requestor)
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
if (mRequestors.Contains(requestor))
|
|
{
|
|
mRequestors.Remove(requestor);
|
|
if (mRequestors.Count == 0)
|
|
{
|
|
Disconnect();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsOwner(Form owner)
|
|
{
|
|
if (mOwner != null && mOwner.IsDisposed)
|
|
{
|
|
mOwner = null;
|
|
}
|
|
return mOwner == owner;
|
|
}
|
|
|
|
public void TakeOwnership(Form owner)
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
if (mOwner == null)
|
|
{
|
|
mOwner = owner;
|
|
ForceStatusUpdate();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ReleaseOwnership(Form owner)
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
if (mOwner == owner)
|
|
{
|
|
mOwner = null;
|
|
ForceStatusUpdate();
|
|
}
|
|
}
|
|
}
|
|
|
|
public MungaGame(Pod pod)
|
|
{
|
|
mPod = pod;
|
|
}
|
|
|
|
public void SetStatus(Image img, string statusMessage)
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
if (mImage != img || mStatusMessage != statusMessage)
|
|
{
|
|
mImage = img;
|
|
mStatusMessage = statusMessage;
|
|
ForceStatusUpdate();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ForceStatusUpdate()
|
|
{
|
|
if (this.OnPodStatusUpdated != null)
|
|
{
|
|
this.OnPodStatusUpdated(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
|
|
private void Connect()
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
if (State != 0)
|
|
{
|
|
return;
|
|
}
|
|
State = MungaConnectionState.Connecting;
|
|
ShutdownSocket();
|
|
mSocket = new MungaSocket();
|
|
mSocket.BeginConnect(mPod.IPAddress, 1501, delegate(IAsyncResult ar)
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
try
|
|
{
|
|
mSocket.EndConnect(ar);
|
|
State = MungaConnectionState.Connected;
|
|
}
|
|
catch
|
|
{
|
|
Disconnect();
|
|
}
|
|
}
|
|
}, null);
|
|
}
|
|
}
|
|
|
|
private void Disconnect()
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
State = MungaConnectionState.Disconnected;
|
|
mAppState = null;
|
|
mAppID = null;
|
|
mNextStateQuery = DateTime.MinValue;
|
|
ShutdownSocket();
|
|
if (Requested)
|
|
{
|
|
Connect();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ShutdownSocket()
|
|
{
|
|
if (mSocket != null)
|
|
{
|
|
try
|
|
{
|
|
mSocket.Shutdown();
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
mSocket = null;
|
|
}
|
|
}
|
|
|
|
public MetaMessage Receive()
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
try
|
|
{
|
|
MetaMessage metaMessage = mSocket.Receive();
|
|
if (metaMessage != null)
|
|
{
|
|
if (metaMessage.Message is StateResponseMessage)
|
|
{
|
|
StateResponseMessage stateResponseMessage = (StateResponseMessage)metaMessage.Message;
|
|
mAppID = stateResponseMessage.Application;
|
|
if (stateResponseMessage.ApplicationState == ApplicationState.WaitingForEgg && stateResponseMessage.ApplicationState != mAppState)
|
|
{
|
|
mEggDelayTimer = DateTime.Now.AddSeconds(5.0);
|
|
mEggSent = DateTime.MinValue;
|
|
mEggAck = false;
|
|
}
|
|
mAppState = stateResponseMessage.ApplicationState;
|
|
}
|
|
else if (metaMessage.Message is AcknowledgeEggFileMessage)
|
|
{
|
|
mEggAck = true;
|
|
}
|
|
}
|
|
return metaMessage;
|
|
}
|
|
catch
|
|
{
|
|
Disconnect();
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Send(int gameID, int fromHost, MungaMessage message)
|
|
{
|
|
lock (mInternalLock)
|
|
{
|
|
try
|
|
{
|
|
mSocket.Send(gameID, fromHost, message);
|
|
}
|
|
catch
|
|
{
|
|
Disconnect();
|
|
}
|
|
}
|
|
}
|
|
}
|