TShock/TShockAPI/SaveManager.cs
stevenh d34199b17d Refactored server stop and world save operations fixing race conditions so as to ensure operations always happen in a predicable order. This fixes output not appearing in the console / log for example. This adds TShock.Utils.StopServer method used by IGA, rcon and the RestAPI.
Fixed console title set not working

Optimised command line parsing

Made Utils a singleton to enforce the fact that only one copy should ever exist

Added name to /v2/user/read output as users can be found by id
2012-02-20 22:31:16 +00:00

123 lines
No EOL
3 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Diagnostics;
using Terraria;
namespace TShockAPI
{
class SaveManager : IDisposable
{
// Singleton
private static readonly SaveManager instance = new SaveManager();
private SaveManager()
{
_saveThread = new Thread(SaveWorker);
_saveThread.Name = "TShock SaveManager Worker";
_saveThread.Start();
}
public static SaveManager Instance { get { return instance; } }
// Producer Consumer
private EventWaitHandle _wh = new AutoResetEvent(false);
private Object _saveLock = new Object();
private Queue<SaveTask> _saveQueue = new Queue<SaveTask>();
private Thread _saveThread;
private int saveQueueCount { get { lock (_saveLock) return _saveQueue.Count; } }
/// <summary>
/// SaveWorld event handler which notifies users that the server may lag
/// </summary>
public void OnSaveWorld(bool resettime = false, HandledEventArgs e = null)
{
TShock.Utils.Broadcast("Saving world. Momentary lag might result from this.", Color.Red);
}
/// <summary>
/// Saves the map data
/// </summary>
/// <param name="wait">wait for all pending saves to finish (default: true)</param>
/// <param name="resetTime">reset the last save time counter (default: false)</param>
/// <param name="direct">use the realsaveWorld method instead of saveWorld event (default: false)</param>
public void SaveWorld(bool wait = true, bool resetTime = false, bool direct = false)
{
EnqueueTask(new SaveTask(resetTime, direct));
if (!wait)
return;
// Wait for all outstanding saves to complete
int count = saveQueueCount;
while (0 != count)
{
Thread.Sleep(50);
count = saveQueueCount;
}
}
/// <summary>
/// Processes any outstanding saves, shutsdown the save thread and returns
/// </summary>
public void Dispose()
{
EnqueueTask(null);
_saveThread.Join();
_wh.Close();
}
private void EnqueueTask(SaveTask task)
{
lock (_saveLock)
{
_saveQueue.Enqueue(task);
}
_wh.Set();
}
private void SaveWorker()
{
while (true)
{
lock (_saveLock)
{
// NOTE: lock for the entire process so wait works in SaveWorld
if (_saveQueue.Count > 0)
{
SaveTask task = _saveQueue.Dequeue();
if (null == task)
return;
else
{
if (task.direct)
{
OnSaveWorld();
WorldGen.realsaveWorld(task.resetTime);
}
else
WorldGen.saveWorld(task.resetTime);
TShock.Utils.Broadcast("World saved.", Color.Yellow);
Log.Info(string.Format("World saved at ({0})", Main.worldPathName));
}
}
}
_wh.WaitOne();
}
}
class SaveTask
{
public bool resetTime { get; set; }
public bool direct { get; set; }
public SaveTask(bool resetTime, bool direct)
{
this.resetTime = resetTime;
this.direct = direct;
}
public override string ToString()
{
return string.Format("resetTime {0}, direct {1}", resetTime, direct);
}
}
}
}