TShock/TShockAPI/SaveManager.cs
2015-07-17 13:49:20 +09:30

163 lines
No EOL
4.4 KiB
C#

/*
TShock, a server mod for Terraria
Copyright (C) 2011-2015 Nyx Studios (fka. The TShock Team)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Threading;
using Terraria;
using Terraria.IO;
using TerrariaApi.Server;
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(WorldSaveEventArgs args)
{
if (TShock.Config.AnnounceSave)
{
// Protect against internal errors causing save failures
// These can be caused by an unexpected error such as a bad or out of date plugin
try
{
TShock.Utils.Broadcast("Saving world. Momentary lag might result from this.", Color.Red);
}
catch (Exception ex)
{
TShock.Log.Error("World saved notification failed");
TShock.Log.Error(ex.ToString());
}
}
}
/// <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
{
// Ensure that save handler errors don't bubble up and cause a recursive call
// These can be caused by an unexpected error such as a bad or out of date plugin
try
{
if (task.direct)
{
OnSaveWorld(new WorldSaveEventArgs());
WorldFile.saveWorld(task.resetTime);
}
else
WorldFile.saveWorld(task.resetTime);
TShock.Utils.Broadcast("World saved.", Color.Yellow);
TShock.Log.Info(string.Format("World saved at ({0})", Main.worldPathName));
}
catch (Exception e)
{
TShock.Log.Error("World saved failed");
TShock.Log.Error(e.ToString());
}
}
}
}
_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);
}
}
}
}