diff --git a/.gitignore b/.gitignore
index b817552..b7279d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -189,3 +189,4 @@ _Pvt_Extensions/
ModelManifest.xml
/Contralto.VC.VC.opendb
/Contralto.VC.db
+/.vs/Contralto/v15/Server/sqlite3
diff --git a/Contralto/AltoSystem.cs b/Contralto/AltoSystem.cs
index a807c5d..21b41a5 100644
--- a/Contralto/AltoSystem.cs
+++ b/Contralto/AltoSystem.cs
@@ -23,6 +23,7 @@ using Contralto.Memory;
using Contralto.Display;
using System.IO;
using System;
+using Contralto.Scripting;
namespace Contralto
{
@@ -41,7 +42,7 @@ namespace Contralto
_keyboard = new Keyboard();
_diskController = new DiskController(this);
_displayController = new DisplayController(this);
- _mouseAndKeyset = new MouseAndKeyset();
+ _mouseAndKeyset = new MouseAndKeyset(this);
_ethernetController = new EthernetController(this);
_orbitController = new OrbitController(this);
_audioDAC = new AudioDAC(this);
@@ -78,6 +79,11 @@ namespace Contralto
_tridentController.Reset();
UCodeMemory.Reset();
+
+ if (ScriptManager.IsRecording)
+ {
+ ScriptManager.Recorder.Command("reset");
+ }
}
///
@@ -137,6 +143,20 @@ namespace Contralto
}
}
}
+
+ //
+ // If we're recording, add a "quit" command to the script, and stop the recording.
+ //
+ if (ScriptManager.IsRecording)
+ {
+ ScriptManager.Recorder.Command(commitDisks ? "quit" : "quit without saving");
+ ScriptManager.StopRecording();
+ }
+
+ if (ScriptManager.IsPlaying)
+ {
+ ScriptManager.StopPlayback();
+ }
}
public void SingleStep()
@@ -187,13 +207,23 @@ namespace Contralto
if (newImage)
{
newPack = InMemoryDiskPack.CreateEmpty(geometry, path);
+
+ if (ScriptManager.IsRecording)
+ {
+ ScriptManager.Recorder.Command(String.Format("new disk {0} {1}", drive, path));
+ }
}
else
{
newPack = InMemoryDiskPack.Load(geometry, path);
+
+ if (ScriptManager.IsRecording)
+ {
+ ScriptManager.Recorder.Command(String.Format("load disk {0} {1}", drive, path));
+ }
}
- _diskController.Drives[drive].LoadPack(newPack);
+ _diskController.Drives[drive].LoadPack(newPack);
}
public void UnloadDiabloDrive(int drive)
@@ -209,6 +239,11 @@ namespace Contralto
_diskController.CommitDisk(drive);
_diskController.Drives[drive].UnloadPack();
+
+ if (ScriptManager.IsRecording)
+ {
+ ScriptManager.Recorder.Command(String.Format("unload disk {0}", drive));
+ }
}
public void LoadTridentDrive(int drive, string path, bool newImage)
@@ -249,10 +284,20 @@ namespace Contralto
if (newImage)
{
newPack = FileBackedDiskPack.CreateEmpty(geometry, path);
+
+ if (ScriptManager.IsRecording)
+ {
+ ScriptManager.Recorder.Command(String.Format("new trident {0} {1}", drive, path));
+ }
}
else
{
newPack = FileBackedDiskPack.Load(geometry, path);
+
+ if (ScriptManager.IsRecording)
+ {
+ ScriptManager.Recorder.Command(String.Format("load trident {0} {1}", drive, path));
+ }
}
_tridentController.Drives[drive].LoadPack(newPack);
@@ -271,6 +316,11 @@ namespace Contralto
_tridentController.CommitDisk(drive);
_tridentController.Drives[drive].UnloadPack();
+
+ if (ScriptManager.IsRecording)
+ {
+ ScriptManager.Recorder.Command(String.Format("unload trident {0}", drive));
+ }
}
diff --git a/Contralto/CPU/CPU.cs b/Contralto/CPU/CPU.cs
index 124ffda..2709f60 100644
--- a/Contralto/CPU/CPU.cs
+++ b/Contralto/CPU/CPU.cs
@@ -146,13 +146,7 @@ namespace Contralto.CPU
{
switch (_currentTask.ExecuteNext())
{
- case InstructionCompletion.TaskSwitch:
- // Invoke the task switch, this will take effect after
- // the NEXT instruction completes, not this one.
- TaskSwitch();
- break;
-
- case InstructionCompletion.Normal:
+ case InstructionCompletion.Normal:
// If we have a new task, switch to it now.
if (_currentTask != _nextTask)
{
@@ -162,6 +156,12 @@ namespace Contralto.CPU
}
break;
+ case InstructionCompletion.TaskSwitch:
+ // Invoke the task switch, this will take effect after
+ // the NEXT instruction completes, not this one.
+ TaskSwitch();
+ break;
+
case InstructionCompletion.MemoryWait:
// We were waiting for memory on this cycle, we do nothing
// (no task switch even if one is pending) in this case.
diff --git a/Contralto/CPU/Tasks/EmulatorTask.cs b/Contralto/CPU/Tasks/EmulatorTask.cs
index 1b05edd..5c1936d 100644
--- a/Contralto/CPU/Tasks/EmulatorTask.cs
+++ b/Contralto/CPU/Tasks/EmulatorTask.cs
@@ -16,6 +16,7 @@
*/
using Contralto.Logging;
+using Contralto.Scripting;
using System;
namespace Contralto.CPU
@@ -159,6 +160,33 @@ namespace Contralto.CPU
_cpu._system.TridentController.STARTF(_busData);
break;
+ //
+ // The following are not actual Alto STARTF functions,
+ // these are used to allow writing Alto programs that can
+ // alter behavior of the emulator. At the moment, these
+ // are all related to scripting, and are only enabled
+ // when a script is running.
+ //
+ case 0x2000:
+ //
+ // Unpause script.
+ //
+ if (ScriptManager.IsPlaying)
+ {
+ ScriptManager.CompleteWait();
+ }
+ break;
+
+ case 0x4000:
+ //
+ // Emulator exit, commit disks.
+ //
+ if (ScriptManager.IsPlaying)
+ {
+ throw new ShutdownException(true);
+ }
+ break;
+
default:
Log.Write(Logging.LogType.Warning, Logging.LogComponent.EmulatorTask, "STARTF for unknown device (code {0})",
Conversion.ToOctal(_busData));
diff --git a/Contralto/Configuration.cs b/Contralto/Configuration.cs
index d805586..fdbbcf1 100644
--- a/Contralto/Configuration.cs
+++ b/Contralto/Configuration.cs
@@ -278,7 +278,7 @@ namespace Contralto
public static void ReadConfiguration()
{
if (Configuration.Platform == PlatformType.Windows
- && Program.StartupArgs.Length == 0)
+ && !string.IsNullOrWhiteSpace(StartupOptions.ConfigurationFile))
{
//
// By default, on Windows we use the app Settings functionality
@@ -366,9 +366,9 @@ namespace Contralto
{
string configFilePath = null;
- if (Program.StartupArgs.Length > 0)
+ if (!string.IsNullOrWhiteSpace(StartupOptions.ConfigurationFile))
{
- configFilePath = Program.StartupArgs[0];
+ configFilePath = StartupOptions.ConfigurationFile;
}
else
{
diff --git a/Contralto/Contralto.csproj b/Contralto/Contralto.csproj
index b6fc8e0..01b2eaf 100644
--- a/Contralto/Contralto.csproj
+++ b/Contralto/Contralto.csproj
@@ -148,9 +148,16 @@
True
Settings.settings
+
+
+
+
+
+
+
-
-
+
+
diff --git a/Contralto/Disk/allgames.dsk b/Contralto/Disk/allgames.dsk
index eff0be8..6b6eeae 100644
Binary files a/Contralto/Disk/allgames.dsk and b/Contralto/Disk/allgames.dsk differ
diff --git a/Contralto/Display/DisplayController.cs b/Contralto/Display/DisplayController.cs
index 69886fe..96faabd 100644
--- a/Contralto/Display/DisplayController.cs
+++ b/Contralto/Display/DisplayController.cs
@@ -143,7 +143,7 @@ namespace Contralto.Display
///
///
///
- private void VerticalBlankScanlineCallback(ulong timeNsec, ulong skewNsec, object context)
+ private void VerticalBlankScanlineCallback(ulong skewNsec, object context)
{
// End of VBlank scanline.
_vblankScanlineCount++;
@@ -189,7 +189,7 @@ namespace Contralto.Display
///
///
///
- private void HorizontalBlankEndCallback(ulong timeNsec, ulong skewNsec, object context)
+ private void HorizontalBlankEndCallback(ulong skewNsec, object context)
{
// Reset scanline word counter
_word = 0;
@@ -220,7 +220,7 @@ namespace Contralto.Display
///
///
///
- private void WordCallback(ulong timeNsec, ulong skewNsec, object context)
+ private void WordCallback(ulong skewNsec, object context)
{
if (_display == null)
{
@@ -254,11 +254,11 @@ namespace Contralto.Display
_display.DrawCursorWord(_scanline, _cursorXLatched, _whiteOnBlack, _cursorRegLatched);
}
- _scanline += 2;
+ _scanline += 2;
if (_scanline >= 808)
{
- // Done with field.
+ // Done with field.
// Draw the completed field to the emulated display.
_display.Render();
@@ -271,7 +271,7 @@ namespace Contralto.Display
// More scanlines to do.
// Run CURT and MRT at end of scanline
- _system.CPU.WakeupTask(TaskType.Cursor);
+ _system.CPU.WakeupTask(TaskType.Cursor);
_system.CPU.WakeupTask(TaskType.MemoryRefresh);
// Schedule HBlank wakeup for end of next HBlank
diff --git a/Contralto/ExecutionController.cs b/Contralto/ExecutionController.cs
index b466acf..f3b3796 100644
--- a/Contralto/ExecutionController.cs
+++ b/Contralto/ExecutionController.cs
@@ -15,6 +15,7 @@
along with ContrAlto. If not, see .
*/
+using Contralto.Scripting;
using System;
using System.Threading;
@@ -23,6 +24,22 @@ namespace Contralto
public delegate bool StepCallbackDelegate();
public delegate void ErrorCallbackDelegate(Exception e);
+ public delegate void ShutdownCallbackDelegate(bool commitDisks);
+
+ public class ShutdownException : Exception
+ {
+ public ShutdownException(bool commitDisks) : base()
+ {
+ _commitDisks = commitDisks;
+ }
+
+ public bool CommitDisks
+ {
+ get { return _commitDisks; }
+ }
+
+ private bool _commitDisks;
+ }
public class ExecutionController
@@ -46,29 +63,56 @@ namespace Contralto
{
_userAbort = true;
- if (_execThread != null)
+ if (System.Threading.Thread.CurrentThread !=
+ _execThread)
{
- _execThread.Join();
- _execThread = null;
+ //
+ // Call is asynchronous, we will wait for the
+ // execution thread to finish.
+ //
+ if (_execThread != null)
+ {
+ _execThread.Join();
+ _execThread = null;
+ }
}
}
public void Reset(AlternateBootType bootType)
{
- bool running = IsRunning;
-
- if (running)
+ if (System.Threading.Thread.CurrentThread ==
+ _execThread)
{
- StopExecution();
+ //
+ // Call is from within the execution thread
+ // so we can just reset the system without worrying
+ // about synchronization.
+ //
+ _system.Reset();
+ _system.PressBootKeys(bootType);
}
- _system.Reset();
- _system.PressBootKeys(bootType);
-
- if (running)
+ else
{
- StartExecution(AlternateBootType.None);
+ //
+ // Call is asynchronous, we need to stop the
+ // execution thread and restart it after resetting
+ // the system.
+ //
+ bool running = IsRunning;
+
+ if (running)
+ {
+ StopExecution();
+ }
+ _system.Reset();
+ _system.PressBootKeys(bootType);
+
+ if (running)
+ {
+ StartExecution(AlternateBootType.None);
+ }
}
- }
+ }
public bool IsRunning
{
@@ -87,6 +131,12 @@ namespace Contralto
set { _errorCallback = value; }
}
+ public ShutdownCallbackDelegate ShutdownCallback
+ {
+ get { return _shutdownCallback; }
+ set { _shutdownCallback = value; }
+ }
+
private void StartAltoExecutionThread()
{
if (_execThread != null && _execThread.IsAlive)
@@ -104,11 +154,29 @@ namespace Contralto
private void ExecuteProc()
{
while (true)
- {
+ {
// Execute a single microinstruction
try
{
_system.SingleStep();
+
+ if (ScriptManager.IsPlaying ||
+ ScriptManager.IsRecording)
+ {
+ ScriptManager.ScriptScheduler.Clock();
+ }
+ }
+ catch(ShutdownException s)
+ {
+ //
+ // We will only actually shut down if someone
+ // is listening to this event.
+ //
+ if (_shutdownCallback != null)
+ {
+ _shutdownCallback(s.CommitDisks);
+ _execAbort = true;
+ }
}
catch (Exception e)
{
@@ -139,6 +207,7 @@ namespace Contralto
private StepCallbackDelegate _stepCallback;
private ErrorCallbackDelegate _errorCallback;
+ private ShutdownCallbackDelegate _shutdownCallback;
private AltoSystem _system;
}
diff --git a/Contralto/IO/DiskController.cs b/Contralto/IO/DiskController.cs
index e1e13e9..70c838e 100644
--- a/Contralto/IO/DiskController.cs
+++ b/Contralto/IO/DiskController.cs
@@ -295,7 +295,7 @@ namespace Contralto.IO
///
///
///
- private void SectorCallback(ulong timeNsec, ulong skewNsec, object context)
+ private void SectorCallback(ulong skewNsec, object context)
{
//
// Next sector; move to next sector and wake up Disk Sector task.
@@ -360,7 +360,7 @@ namespace Contralto.IO
///
///
///
- private void WordCallback(ulong timeNsec, ulong skewNsec, object context)
+ private void WordCallback(ulong skewNsec, object context)
{
SpinDisk();
@@ -385,7 +385,7 @@ namespace Contralto.IO
///
///
///
- private void SeclateCallback(ulong timeNsec, ulong skewNsec, object context)
+ private void SeclateCallback(ulong skewNsec, object context)
{
if (_seclateEnable)
{
@@ -635,7 +635,7 @@ namespace Contralto.IO
return ((_kAdr & 0x00c0) >> 6) == 2 || ((_kAdr & 0x00c0) >> 6) == 3;
}
- private void SeekCallback(ulong timeNsec, ulong skewNsec, object context)
+ private void SeekCallback(ulong skewNsec, object context)
{
if (SelectedDrive.Cylinder < _destCylinder)
{
diff --git a/Contralto/IO/DoverROS.cs b/Contralto/IO/DoverROS.cs
index ffaa637..9dd30af 100644
--- a/Contralto/IO/DoverROS.cs
+++ b/Contralto/IO/DoverROS.cs
@@ -474,7 +474,7 @@ namespace Contralto.IO
///
///
///
- private void PrintEngineCallback(ulong timestampNsec, ulong delta, object context)
+ private void PrintEngineCallback(ulong delta, object context)
{
Log.Write(LogComponent.DoverROS, "Scanline {0} (sendvideo {1})", _printEngineTimestep, _sendVideo);
switch (_state)
diff --git a/Contralto/IO/EthernetController.cs b/Contralto/IO/EthernetController.cs
index 43613c1..0f1e5a2 100644
--- a/Contralto/IO/EthernetController.cs
+++ b/Contralto/IO/EthernetController.cs
@@ -291,7 +291,7 @@ namespace Contralto.IO
_system.Scheduler.Schedule(_fifoTransmitWakeupEvent);
}
- private void OutputFifoCallback(ulong timeNsec, ulong skewNsec, object context)
+ private void OutputFifoCallback(ulong skewNsec, object context)
{
bool end = (bool)context;
@@ -415,21 +415,21 @@ namespace Contralto.IO
///
///
///
- private void InputHandler(ulong timeNsec, ulong skewNsec, object context)
+ private void InputHandler(ulong skewNsec, object context)
{
switch(_inputState)
{
case InputState.ReceiverOff:
- // Receiver is off, if we have any incoming packets, they are ignored.
- // TODO: would it make sense to expire really old packets (say more than a couple of seconds old)
- // so that the receiver doesn't pick up ancient history the next time it runs?
- // We already cycle out packets as new ones come in, so this would only be an issue on very quiet networks.
- // (And even then I don't know if it's really an issue.)
+ // Receiver is off, if we have any incoming packets, they are dropped.
+ // (If we leave packets in the queue while the receiver is off, this can cause
+ // stale data to be picked up when the receiver is turned back on, which can in
+ // turn cause unexpected behavior.)
_receiverLock.EnterReadLock();
if (_nextPackets.Count > 0)
{
- Log.Write(LogComponent.EthernetPacket, "Receiver is off, ignoring incoming packet from packet queue.");
+ Log.Write(LogComponent.EthernetPacket, "Receiver is off, dropping incoming packets from packet queue.");
+ _nextPackets.Clear();
}
_receiverLock.ExitReadLock();
_inputPollActive = false;
diff --git a/Contralto/IO/Keyboard.cs b/Contralto/IO/Keyboard.cs
index 4a39971..f16e552 100644
--- a/Contralto/IO/Keyboard.cs
+++ b/Contralto/IO/Keyboard.cs
@@ -19,6 +19,7 @@ using System.Collections.Generic;
using Contralto.Memory;
using Contralto.CPU;
+using Contralto.Scripting;
namespace Contralto.IO
{
@@ -115,7 +116,7 @@ namespace Contralto.IO
public Keyboard()
{
InitMap();
- Reset();
+ Reset();
}
public void Reset()
@@ -125,8 +126,8 @@ namespace Contralto.IO
}
public ushort Read(int address, TaskType task, bool extendedMemoryReference)
- {
- // keyboard word is inverted
+ {
+ // keyboard word is inverted
return (ushort)~_keyWords[address - 0xfe1c]; // TODO: move to constant.
}
@@ -141,21 +142,31 @@ namespace Contralto.IO
// If we had been holding boot keys, release them now that a real user is pressing a key.
if (_bootKeysPressed)
{
- Reset();
+ Reset();
}
AltoKeyBit bits = _keyMap[key];
_keyWords[bits.Word] |= bits.Bitmask;
+
+ if (ScriptManager.IsRecording)
+ {
+ ScriptManager.Recorder.KeyDown(key);
+ }
}
public void KeyUp(AltoKey key)
{
AltoKeyBit bits = _keyMap[key];
_keyWords[bits.Word] &= (ushort)~bits.Bitmask;
+
+ if (ScriptManager.IsRecording)
+ {
+ ScriptManager.Recorder.KeyUp(key);
+ }
}
public void PressBootKeys(ushort bootAddress, bool netBoot)
- {
+ {
for (int i = 0; i < 16; i++)
{
if ((bootAddress & (0x8000 >> i)) != 0)
diff --git a/Contralto/IO/MouseAndKeyset.cs b/Contralto/IO/MouseAndKeyset.cs
index cda100d..30fe536 100644
--- a/Contralto/IO/MouseAndKeyset.cs
+++ b/Contralto/IO/MouseAndKeyset.cs
@@ -17,7 +17,9 @@
using Contralto.CPU;
using Contralto.Memory;
+using Contralto.Scripting;
using System;
+using System.Collections.Generic;
using System.Threading;
namespace Contralto.IO
@@ -51,9 +53,10 @@ namespace Contralto.IO
///
public class MouseAndKeyset : IMemoryMappedDevice
{
- public MouseAndKeyset()
+ public MouseAndKeyset(AltoSystem system)
{
- _lock = new ReaderWriterLockSlim();
+ _system = system;
+ _lock = new ReaderWriterLockSlim();
Reset();
}
@@ -61,6 +64,9 @@ namespace Contralto.IO
{
_keyset = 0;
_buttons = AltoMouseButton.None;
+ _moves = new Queue();
+ _currentMove = null;
+ _pollCounter = 0;
}
public ushort Read(int address, TaskType task, bool extendedMemoryReference)
@@ -75,26 +81,47 @@ namespace Contralto.IO
public void MouseMove(int dx, int dy)
{
+ // Calculate number of steps in x and y to be decremented every call to PollMouseBits
+ MouseMovement nextMove = new MouseMovement(Math.Abs(dx), Math.Abs(dy), Math.Sign(dx), Math.Sign(dy));
+
_lock.EnterWriteLock();
- // Calculate number of steps in x and y to be decremented every call to PollMouseBits
- _xSteps = Math.Abs(dx);
- _xDir = Math.Sign(dx);
+ _moves.Enqueue(nextMove);
+
+ if (ScriptManager.IsRecording)
+ {
+ ScriptManager.Recorder.MouseMoveRelative(dx, dy);
+ }
- _ySteps = Math.Abs(dy);
- _yDir = Math.Sign(dy);
-
_lock.ExitWriteLock();
}
public void MouseDown(AltoMouseButton button)
{
_buttons |= button;
+
+ if (ScriptManager.IsRecording)
+ {
+ //
+ // Record the absolute position of the mouse (as held in MOUSELOC in system memory).
+ // All other mouse movements in the script will be recorded relative to this point.
+ //
+ //int x = _system.Memory.Read(0x114, CPU.TaskType.Ethernet, false);
+ //int y = _system.Memory.Read(0x115, CPU.TaskType.Ethernet, false);
+ //ScriptManager.Recorder.MouseMoveAbsolute(x, y);
+
+ ScriptManager.Recorder.MouseDown(button);
+ }
}
public void MouseUp(AltoMouseButton button)
{
_buttons ^= button;
+
+ if (ScriptManager.IsRecording)
+ {
+ ScriptManager.Recorder.MouseUp(button);
+ }
}
public void KeysetDown(AltoKeysetKey key)
@@ -129,63 +156,110 @@ namespace Contralto.IO
ushort bits = 0;
_lock.EnterReadLock();
- // TODO: optimize this
- if (_yDir == -1 && _xDir == 0)
- {
- bits = 1;
- }
- else if (_yDir == 1 && _xDir == 0)
- {
- bits = 2;
- }
- else if (_yDir == 0 && _xDir == -1)
- {
- bits = 3;
- }
- else if (_yDir == -1 && _xDir == -1)
- {
- bits = 4;
- }
- else if (_yDir == 1 && _xDir == -1)
- {
- bits = 5;
- }
- else if (_yDir == 0 && _xDir == 1)
- {
- bits = 6;
- }
- else if (_yDir == -1 && _xDir == 1)
- {
- bits = 7;
- }
- else if (_yDir == 1 && _xDir == 1)
- {
- bits = 8;
- }
- // Move the mouse closer to its destination
- if (_xSteps > 0)
+ if (_currentMove == null && _moves.Count > 0)
{
- _mouseX += _xDir;
- _xSteps--;
+ _currentMove = _moves.Dequeue();
+ }
- if (_xSteps == 0)
+ //
+ // <-MOUSE is invoked by the Memory Refresh Task once per scanline (including during vblank) which
+ // works out to about 13,000 times a second. To more realistically simulate the movement of a mouse
+ // across a desk, we return actual mouse movement data only periodically.
+ //
+ if (_currentMove != null && (_pollCounter % _currentMove.PollRate) == 0)
+ {
+
+ //
+ // Choose a direction. We do not provide movements in both X and Y at the same time;
+ // this is solely to avoid a microcode bug that causes erroneous movements in such cases
+ // (which then plays havoc with scripting and absolute coordinates.)
+ // (It is also the case that on the real hardware, such movements are extremely rare due to
+ // the nature of the hardware involved).
+ //
+ int dx = _currentMove.DX;
+ int dy = _currentMove.DY;
+
+ if (dx != 0 && dy != 0)
{
- _xDir = 0;
- }
- }
-
- if (_ySteps > 0)
- {
- _mouseY += _yDir;
- _ySteps--;
-
- if (_ySteps == 0)
- {
- _yDir = 0;
+ // Choose just one of the two directions to move in.
+ if (_currentDirection)
+ {
+ dx = 0;
+ }
+ else
+ {
+ dy = 0;
+ }
+
+ _currentDirection = !_currentDirection;
+ }
+
+
+ if (dy == -1 && dx == 0)
+ {
+ bits = 1;
+ }
+ else if (dy == 1 && dx == 0)
+ {
+ bits = 2;
+ }
+ else if (dy == 0 && dx == -1)
+ {
+ bits = 3;
+ }
+ else if (dy == -1 && dx == -1)
+ {
+ bits = 4;
+ }
+ else if (dy == 1 && dx == -1)
+ {
+ bits = 5;
+ }
+ else if (dy == 0 && dx == 1)
+ {
+ bits = 6;
+ }
+ else if (dy == -1 && dx == 1)
+ {
+ bits = 7;
+ }
+ else if (dy == 1 && dx == 1)
+ {
+ bits = 8;
+ }
+
+ //
+ // Move the mouse closer to its destination in either X or Y
+ // (but not both)
+ if (_currentMove.XSteps > 0 && dx != 0)
+ {
+ _currentMove.XSteps--;
+
+ if (_currentMove.XSteps == 0)
+ {
+ _currentMove.DX = 0;
+ }
+ }
+
+ if (_currentMove.YSteps > 0 && dy != 0)
+ {
+ _currentMove.YSteps--;
+
+ if (_currentMove.YSteps == 0)
+ {
+ _currentMove.DY = 0;
+ }
+ }
+
+ if (_currentMove.XSteps == 0 && _currentMove.YSteps == 0)
+ {
+ _currentMove = null;
}
}
+
_lock.ExitReadLock();
+ _pollCounter++;
return bits;
}
@@ -200,27 +274,60 @@ namespace Contralto.IO
new MemoryRange(0xfe18, 0xfe1b), // UTILIN: 177030-177033
};
+ AltoSystem _system;
+
// Mouse buttons:
AltoMouseButton _buttons;
// Keyset switches:
AltoKeysetKey _keyset;
- ///
- /// Where the mouse is currently reported to be
- ///
- private int _mouseX;
- private int _mouseY;
+ private ReaderWriterLockSlim _lock;
+
+ // Used to control the rate of mouse movement data
+ //
+ public int _pollCounter;
///
/// Where the mouse is moving to every time PollMouseBits is called.
- ///
- private int _xSteps;
- private int _xDir;
- private double _ySteps;
- private int _yDir;
+ ///
+ private Queue _moves;
+ private MouseMovement _currentMove;
+ private bool _currentDirection;
+
+ private class MouseMovement
+ {
+ public MouseMovement(int xsteps, int ysteps, int dx, int dy)
+ {
+ XSteps = xsteps;
+ YSteps = ysteps;
+ DX = dx;
+ DY = dy;
+
+ //
+ // Calculate the rate at which mouse data should be returned in PollMouseBits,
+ // this is a function of the distance moved in this movement. We assume that the
+ // movement occurred in 1/60th of a second; PollMouseBits is invoked (via <-MOUSE)
+ // by the MRT approximately every 1/13000th of a second.
+ // This is all approximate and not expected to be completely accurate.
+ //
+ double distance = Math.Sqrt(Math.Pow(xsteps, 2) + Math.Pow(ysteps, 2));
+
+ PollRate = (int)((13000.0 / 120.0) / (distance + 1));
+
+ if (PollRate == 0)
+ {
+ PollRate = 1;
+ }
+ }
+
+ public int XSteps;
+ public int YSteps;
+ public int DX;
+ public int DY;
+ public int PollRate;
+ }
- private ReaderWriterLockSlim _lock;
}
}
diff --git a/Contralto/IO/OrbitController.cs b/Contralto/IO/OrbitController.cs
index bde5f97..59ca781 100644
--- a/Contralto/IO/OrbitController.cs
+++ b/Contralto/IO/OrbitController.cs
@@ -596,7 +596,7 @@ namespace Contralto.IO
_image[x, wordAddress] = (ushort)(inputWord | inkBit);
}
- private void RefreshCallback(ulong timeNsec, ulong skewNsec, object context)
+ private void RefreshCallback(ulong skewNsec, object context)
{
_refresh = true;
diff --git a/Contralto/IO/TridentController.cs b/Contralto/IO/TridentController.cs
index f65c012..1737eb8 100644
--- a/Contralto/IO/TridentController.cs
+++ b/Contralto/IO/TridentController.cs
@@ -305,7 +305,7 @@ namespace Contralto.IO
}
}
- private void OutputFifoCallback(ulong timeNsec, ulong skewNsec, object context)
+ private void OutputFifoCallback(ulong skewNsec, object context)
{
switch (_commandState)
{
@@ -701,7 +701,7 @@ namespace Contralto.IO
}
}
- private void ReadWordCallback(ulong timeNsec, ulong skewNsec, object context)
+ private void ReadWordCallback(ulong skewNsec, object context)
{
if (_readWordCount > 0)
{
@@ -862,7 +862,7 @@ namespace Contralto.IO
}
}
- private void SectorCallback(ulong timeNsec, ulong skewNsec, object context)
+ private void SectorCallback(ulong skewNsec, object context)
{
// Move to the next sector if the controller is running
// and the disk is ready.
diff --git a/Contralto/IO/TridentDrive.cs b/Contralto/IO/TridentDrive.cs
index e9e7788..136254e 100644
--- a/Contralto/IO/TridentDrive.cs
+++ b/Contralto/IO/TridentDrive.cs
@@ -271,7 +271,7 @@ namespace Contralto.IO
}
}
- private void SeekCallback(ulong timeNsec, ulong skewNsec, object context)
+ private void SeekCallback(ulong skewNsec, object context)
{
Log.Write(LogComponent.TridentDisk, "Seek to {0} complete.", _destCylinder);
diff --git a/Contralto/Logging/Log.cs b/Contralto/Logging/Log.cs
index 744d050..405a0db 100644
--- a/Contralto/Logging/Log.cs
+++ b/Contralto/Logging/Log.cs
@@ -53,6 +53,7 @@ namespace Contralto.Logging
TridentController = 0x200000,
TridentDisk = 0x400000,
+ Scripting = 0x2000000,
Debug = 0x40000000,
All = 0x7fffffff
}
diff --git a/Contralto/Memory/Memory.cs b/Contralto/Memory/Memory.cs
index 950bcd3..d5cd494 100644
--- a/Contralto/Memory/Memory.cs
+++ b/Contralto/Memory/Memory.cs
@@ -83,7 +83,7 @@ namespace Contralto.Memory
// Check for XM registers; this occurs regardless of XM flag since it's in the I/O page.
if (address >= _xmBanksStart && address < _xmBanksStart + 16)
{
- // NB: While not specified in documentatino, some code (IFS in particular) relies on the fact that
+ // NB: While not specified in documentation, some code (IFS in particular) relies on the fact that
// the upper 12 bits of the bank registers are all 1s.
return (ushort)(0xfff0 | _xmBanks[address - _xmBanksStart]);
}
@@ -113,7 +113,7 @@ namespace Contralto.Memory
}
else
{
- address += 0x10000 * GetBankNumber(task, extendedMemory);
+ address += 0x10000 * GetBankNumber(task, extendedMemory);
_mem[address] = data;
}
}
diff --git a/Contralto/Program.cs b/Contralto/Program.cs
index 20cbf01..f77cbb3 100644
--- a/Contralto/Program.cs
+++ b/Contralto/Program.cs
@@ -15,30 +15,66 @@
along with ContrAlto. If not, see .
*/
+using Contralto.Scripting;
using Contralto.SdlUI;
using System;
using System.Windows.Forms;
namespace Contralto
{
+ public static class StartupOptions
+ {
+ public static string ConfigurationFile;
+
+ public static string ScriptFile;
+ }
+
class Program
{
[STAThread]
static void Main(string[] args)
{
//
- // Check for command-line arguments.
- // We expect at most one argument, specifying a configuration file to load.
- //
- StartupArgs = args;
- if (args.Length > 1)
+ // Check for command-line arguments.
+ //
+ if (args.Length > 0)
{
- Console.WriteLine("usage: Contralto ");
- return;
- }
+ for (int i = 0; i < args.Length; i++)
+ {
+ switch (args[i++].ToLowerInvariant())
+ {
+ case "-config":
+ if (i < args.Length)
+ {
+ StartupOptions.ConfigurationFile = args[i];
+ }
+ else
+ {
+ PrintUsage();
+ return;
+ }
+ break;
- // Handle command-line args
- PrintHerald();
+ case "-script":
+ if (i < args.Length)
+ {
+ StartupOptions.ScriptFile = args[i];
+ }
+ else
+ {
+ PrintUsage();
+ return;
+ }
+ break;
+
+ default:
+ PrintUsage();
+ return;
+ }
+ }
+ }
+
+ PrintHerald();
_system = new AltoSystem();
@@ -122,8 +158,6 @@ namespace Contralto
}
}
- public static string[] StartupArgs;
-
private static void OnProcessExit(object sender, EventArgs e)
{
Console.WriteLine("Exiting...");
@@ -138,11 +172,16 @@ namespace Contralto
private static void PrintHerald()
{
- Console.WriteLine("ContrAlto v{0} (c) 2015-2017 Living Computers: Museum+Labs.", typeof(Program).Assembly.GetName().Version);
+ Console.WriteLine("ContrAlto v{0} (c) 2015-2018 Living Computers: Museum+Labs.", typeof(Program).Assembly.GetName().Version);
Console.WriteLine("Bug reports to joshd@livingcomputers.org");
Console.WriteLine();
- }
-
+ }
+
+ private static void PrintUsage()
+ {
+ Console.WriteLine("Usage: ContrAlto [-config ] [-script ]");
+ }
+
private static AltoSystem _system;
}
}
diff --git a/Contralto/Properties/AssemblyInfo.cs b/Contralto/Properties/AssemblyInfo.cs
index 95db7f9..e48733f 100644
--- a/Contralto/Properties/AssemblyInfo.cs
+++ b/Contralto/Properties/AssemblyInfo.cs
@@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Living Computers: Museum+Labs")]
[assembly: AssemblyProduct("ContrAlto")]
-[assembly: AssemblyCopyright("Copyright © Living Computers: Museum+Labs 2015-2017")]
+[assembly: AssemblyCopyright("Copyright © Living Computers: Museum+Labs 2015-2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.2.2")]
-[assembly: AssemblyFileVersion("1.2.2.0")]
+[assembly: AssemblyVersion("1.2.3")]
+[assembly: AssemblyFileVersion("1.2.3.0")]
diff --git a/Contralto/Properties/Resources.Designer.cs b/Contralto/Properties/Resources.Designer.cs
index f1afe5c..8413905 100644
--- a/Contralto/Properties/Resources.Designer.cs
+++ b/Contralto/Properties/Resources.Designer.cs
@@ -60,6 +60,69 @@ namespace Contralto.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to Alto Diablo Disk Images (*.dsk, *.dsk44)|*.dsk;*.dsk44|Diablo 31 Disk Images (*.dsk)|*.dsk|Diablo 44 Disk Images (*.dsk44)|*.dsk44|All Files (*.*)|*.*.
+ ///
+ internal static string DiabloFilter {
+ get {
+ return ResourceManager.GetString("DiabloFilter", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to An error occurred while creating new disk image: {0}.
+ ///
+ internal static string DiskCreateErrorText {
+ get {
+ return ResourceManager.GetString("DiskCreateErrorText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Image creation error.
+ ///
+ internal static string DiskCreateErrorTitle {
+ get {
+ return ResourceManager.GetString("DiskCreateErrorTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to An error occurred while loading image: {0}.
+ ///
+ internal static string DiskLoadErrorText {
+ get {
+ return ResourceManager.GetString("DiskLoadErrorText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Image load error.
+ ///
+ internal static string DiskLoadErrorTitle {
+ get {
+ return ResourceManager.GetString("DiskLoadErrorTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Select image to load into {0} drive {1}.
+ ///
+ internal static string DiskLoadTitle {
+ get {
+ return ResourceManager.GetString("DiskLoadTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Select path for new {0} image for drive {1}.
+ ///
+ internal static string DiskNewTitle {
+ get {
+ return ResourceManager.GetString("DiskNewTitle", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized resource of type System.Drawing.Bitmap.
///
@@ -80,6 +143,24 @@ namespace Contralto.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to Unable to save {0} disk {1}'s contents during unload. Error {2]. Any changes have been lost..
+ ///
+ internal static string DiskSaveErrorText {
+ get {
+ return ResourceManager.GetString("DiskSaveErrorText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Image unload error.
+ ///
+ internal static string DiskSaveErrorTitle {
+ get {
+ return ResourceManager.GetString("DiskSaveErrorTitle", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized resource of type System.Drawing.Bitmap.
///
@@ -109,5 +190,185 @@ namespace Contralto.Properties {
return ((System.Drawing.Bitmap)(obj));
}
}
+
+ ///
+ /// Looks up a localized string similar to Alto Mouse/Keyboard captured. Press Alt to release..
+ ///
+ internal static string MouseCaptureActiveText {
+ get {
+ return ResourceManager.GetString("MouseCaptureActiveText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Click on display to capture Alto Mouse/Keyboard..
+ ///
+ internal static string MouseCaptureInactiveText {
+ get {
+ return ResourceManager.GetString("MouseCaptureInactiveText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to <no image loaded>.
+ ///
+ internal static string NoImageLoadedText {
+ get {
+ return ResourceManager.GetString("NoImageLoadedText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Script playback in Progress..
+ ///
+ internal static string PlaybackInProgressText {
+ get {
+ return ResourceManager.GetString("PlaybackInProgressText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Script recording in Progress..
+ ///
+ internal static string RecordingInProgressText {
+ get {
+ return ResourceManager.GetString("RecordingInProgressText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Screenshot.png.
+ ///
+ internal static string ScreenshotDefaultFileName {
+ get {
+ return ResourceManager.GetString("ScreenshotDefaultFileName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Could not save screenshot. Check the specified filename and path and try again..
+ ///
+ internal static string ScreenshotErrorText {
+ get {
+ return ResourceManager.GetString("ScreenshotErrorText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to PNG Images (*.png)|*.png|All Files (*.*)|*.*.
+ ///
+ internal static string ScreenshotFilter {
+ get {
+ return ResourceManager.GetString("ScreenshotFilter", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Select destination for screenshot..
+ ///
+ internal static string ScreenshotTitle {
+ get {
+ return ResourceManager.GetString("ScreenshotTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Alto Script Files (*.script)|*.script|All Files (*.*)|*.*.
+ ///
+ internal static string ScriptFilter {
+ get {
+ return ResourceManager.GetString("ScriptFilter", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Select script file to play.
+ ///
+ internal static string ScriptPlaybackTitle {
+ get {
+ return ResourceManager.GetString("ScriptPlaybackTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Select path for script file to record.
+ ///
+ internal static string ScriptRecordTitle {
+ get {
+ return ResourceManager.GetString("ScriptRecordTitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Play Script....
+ ///
+ internal static string StartPlaybackText {
+ get {
+ return ResourceManager.GetString("StartPlaybackText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Record Script....
+ ///
+ internal static string StartRecordingText {
+ get {
+ return ResourceManager.GetString("StartRecordingText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Stop Playback.
+ ///
+ internal static string StopPlaybackText {
+ get {
+ return ResourceManager.GetString("StopPlaybackText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Stop Recording.
+ ///
+ internal static string StopRecordingText {
+ get {
+ return ResourceManager.GetString("StopRecordingText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Alto Stopped due to error. See Debugger..
+ ///
+ internal static string SystemErrorText {
+ get {
+ return ResourceManager.GetString("SystemErrorText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Alto Running..
+ ///
+ internal static string SystemRunningText {
+ get {
+ return ResourceManager.GetString("SystemRunningText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Alto Stopped..
+ ///
+ internal static string SystemStoppedText {
+ get {
+ return ResourceManager.GetString("SystemStoppedText", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Alto Trident Disk Images (*.dsk80, *.dsk300)|*.dsk80;*.dsk300|Trident T80 Disk Images (*.dsk80)|*.dsk80|Trident T300 Disk Images (*.dsk300)|*.dsk300|All Files (*.*)|*.*.
+ ///
+ internal static string TridentFilter {
+ get {
+ return ResourceManager.GetString("TridentFilter", resourceCulture);
+ }
+ }
}
}
diff --git a/Contralto/Properties/Resources.resx b/Contralto/Properties/Resources.resx
index f616cb2..9aba521 100644
--- a/Contralto/Properties/Resources.resx
+++ b/Contralto/Properties/Resources.resx
@@ -117,6 +117,27 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ Alto Diablo Disk Images (*.dsk, *.dsk44)|*.dsk;*.dsk44|Diablo 31 Disk Images (*.dsk)|*.dsk|Diablo 44 Disk Images (*.dsk44)|*.dsk44|All Files (*.*)|*.*
+
+
+ An error occurred while creating new disk image: {0}
+
+
+ Image creation error
+
+
+ An error occurred while loading image: {0}
+
+
+ Image load error
+
+
+ Select image to load into {0} drive {1}
+
+
+ Select path for new {0} image for drive {1}
+
..\Resources\DiskNoAccess.bmp;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
@@ -124,6 +145,12 @@
..\Resources\DiskRead.bmp;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+ Unable to save {0} disk {1}'s contents during unload. Error {2]. Any changes have been lost.
+
+
+ Image unload error
+
..\resources\diskseek.bmp;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
@@ -133,4 +160,64 @@
..\Resources\dragon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+ Alto Mouse/Keyboard captured. Press Alt to release.
+
+
+ Click on display to capture Alto Mouse/Keyboard.
+
+
+ <no image loaded>
+
+
+ Script playback in Progress.
+
+
+ Script recording in Progress.
+
+
+ Screenshot.png
+
+
+ Could not save screenshot. Check the specified filename and path and try again.
+
+
+ PNG Images (*.png)|*.png|All Files (*.*)|*.*
+
+
+ Select destination for screenshot.
+
+
+ Alto Script Files (*.script)|*.script|All Files (*.*)|*.*
+
+
+ Select script file to play
+
+
+ Select path for script file to record
+
+
+ Play Script...
+
+
+ Record Script...
+
+
+ Stop Playback
+
+
+ Stop Recording
+
+
+ Alto Stopped due to error. See Debugger.
+
+
+ Alto Running.
+
+
+ Alto Stopped.
+
+
+ Alto Trident Disk Images (*.dsk80, *.dsk300)|*.dsk80;*.dsk300|Trident T80 Disk Images (*.dsk80)|*.dsk80|Trident T300 Disk Images (*.dsk300)|*.dsk300|All Files (*.*)|*.*
+
\ No newline at end of file
diff --git a/Contralto/Scheduler.cs b/Contralto/Scheduler.cs
index 86f26e2..9ec5107 100644
--- a/Contralto/Scheduler.cs
+++ b/Contralto/Scheduler.cs
@@ -18,17 +18,15 @@
using System;
using System.Collections.Generic;
-
namespace Contralto
{
///
/// The SchedulerEventCallback describes a delegate that is invoked whenever a scheduled event has
/// reached its due-date and is fired.
- ///
- /// The current Alto time (in nsec)
+ ///
/// The delta between the requested exec time and the actual exec time (in nsec)
/// An object containing context useful to the scheduler of the event
- public delegate void SchedulerEventCallback(ulong timeNsec, ulong skewNsec, object context);
+ public delegate void SchedulerEventCallback(ulong skewNsec, object context);
///
/// An Event encapsulates a callback and associated context that is scheduled for a future timestamp.
@@ -77,12 +75,17 @@ namespace Contralto
///
/// The Scheduler class provides infrastructure for scheduling Alto time-based hardware events
/// (for example, sector marks, or video task wakeups).
+ ///
+ /// Note that the Scheduler is not thread-safe and must only be used from the emulation thread,
+ /// or else things will break. This is not optimal -- having a thread-safe scheduler would make
+ /// it easier/cleaner to deal with asynchronous things like ethernet packets and scripting events
+ /// but doing so incurs about a 10% performance penalty so it's been avoided.
///
public class Scheduler
{
public Scheduler()
{
- Reset();
+ Reset();
}
public ulong CurrentTimeNsec
@@ -105,13 +108,14 @@ namespace Contralto
//
// See if we have any events waiting to fire at this timestep.
- //
+ //
while (_schedule.Top != null && _currentTimeNsec >= _schedule.Top.TimestampNsec)
{
// Pop the top event and fire the callback.
- Event e = _schedule.Pop();
- e.EventCallback(_currentTimeNsec, _currentTimeNsec - e.TimestampNsec, e.Context);
- }
+ Event e = _schedule.Pop();
+
+ e.EventCallback(_currentTimeNsec - e.TimestampNsec, e.Context);
+ }
}
///
@@ -129,27 +133,19 @@ namespace Contralto
#endif
e.TimestampNsec += _currentTimeNsec;
+
_schedule.Push(e);
return e;
}
- ///
- /// Remove an event from the schedule.
- ///
- ///
- public void CancelEvent(Event e)
- {
- _schedule.Remove(e);
- }
-
private ulong _currentTimeNsec;
private SchedulerQueue _schedule;
// 170nsec is approximately one Alto system clock cycle and is the time-base for
// the scheduler.
- private const ulong _timeStepNsec = 170;
+ private const ulong _timeStepNsec = 170;
}
///
@@ -215,7 +211,7 @@ namespace Contralto
Event e = _top;
_queue.RemoveFirst();
- _top = _queue.First.Value;
+ _top = _queue.First != null ? _queue.First.Value : null;
return e;
}
diff --git a/Contralto/SdlUI/ConsoleExecutor.cs b/Contralto/Scripting/CommandExecutor.cs
similarity index 90%
rename from Contralto/SdlUI/ConsoleExecutor.cs
rename to Contralto/Scripting/CommandExecutor.cs
index a996ad7..8f1743d 100644
--- a/Contralto/SdlUI/ConsoleExecutor.cs
+++ b/Contralto/Scripting/CommandExecutor.cs
@@ -21,34 +21,48 @@ using System.Reflection;
using System.Text;
using System.IO;
-namespace Contralto.SdlUI
+namespace Contralto.Scripting
{
+ public class MethodInvokeInfo
+ {
+ public MethodInvokeInfo(MethodInfo method, object instance)
+ {
+ if (method == null || instance == null)
+ {
+ throw new ArgumentNullException("method and instance must be non-null");
+ }
+
+ Method = method;
+ Instance = instance;
+ }
+
+ public MethodInfo Method;
+ public object Instance;
+ }
///
/// Defines a node in the debug command tree.
///
public class DebuggerCommand
{
- public DebuggerCommand(object instance, string name, String description, String usage, MethodInfo method)
+ public DebuggerCommand(string name, String description, String usage, MethodInvokeInfo methodInvoke)
{
- Instance = instance;
Name = name.Trim().ToLower();
Description = description;
Usage = usage;
- Methods = new List(4);
+ Methods = new List(4);
- if (method != null)
+ if (methodInvoke != null)
{
- Methods.Add(method);
+ Methods.Add(methodInvoke);
}
SubCommands = new List();
}
-
- public object Instance;
+
public string Name;
public string Description;
public string Usage;
- public List Methods;
+ public List Methods;
public List SubCommands;
public override string ToString()
@@ -63,7 +77,7 @@ namespace Contralto.SdlUI
}
}
- public void AddSubNode(List words, MethodInfo method, object instance)
+ public void AddSubNode(List words, MethodInvokeInfo methodInfo)
{
// We should never hit this case.
if (words.Count == 0)
@@ -77,13 +91,13 @@ namespace Contralto.SdlUI
if (subNode == null)
{
// No, it has not -- create one and add it now.
- subNode = new DebuggerCommand(instance, words[0], null, null, null);
+ subNode = new DebuggerCommand(words[0], null, null, null);
this.SubCommands.Add(subNode);
if (words.Count == 1)
{
// This is the last stop -- set the method and be done with it now.
- subNode.Methods.Add(method);
+ subNode.Methods.Add(methodInfo);
// early return.
return;
@@ -98,10 +112,10 @@ namespace Contralto.SdlUI
// If we're on the last word at this point then this is an overloaded command.
// Check that we don't have any other commands with this number of arguments.
//
- int argCount = method.GetParameters().Length;
- foreach (MethodInfo info in subNode.Methods)
+ int argCount = methodInfo.Method.GetParameters().Length;
+ foreach (MethodInvokeInfo info in subNode.Methods)
{
- if (info.GetParameters().Length == argCount)
+ if (info.Method.GetParameters().Length == argCount)
{
throw new InvalidOperationException("Duplicate overload for console command");
}
@@ -110,7 +124,7 @@ namespace Contralto.SdlUI
//
// We're ok. Add it to the method list.
//
- subNode.Methods.Add(method);
+ subNode.Methods.Add(methodInfo);
// and return early.
return;
@@ -119,7 +133,7 @@ namespace Contralto.SdlUI
// We have more words to go.
words.RemoveAt(0);
- subNode.AddSubNode(words, method, instance);
+ subNode.AddSubNode(words, methodInfo);
}
public DebuggerCommand FindSubNodeByName(string name)
@@ -138,37 +152,21 @@ namespace Contralto.SdlUI
return found;
}
}
-
- public class ConsoleExecutor
+
+ public enum CommandResult
{
- public ConsoleExecutor(params object[] commandObjects)
+ Normal,
+ Quit,
+ QuitNoSave,
+ }
+
+ public class CommandExecutor
+ {
+ public CommandExecutor(params object[] commandObjects)
{
List