Files
opensim-addon-modules-exp/modules/OpenSimRegionWarmupHealthGuard/addon-modules/Modules/RegionWarmup.cs
T

216 lines
7.6 KiB
C#

using System;
using System.Linq;
using System.Reflection;
using System.Timers;
using log4net;
using Mono.Addins;
using Nini.Config;
using OpenSim.Framework;
using OpenSim.Framework.Console;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
[assembly: Addin("RegionWarmup", "1.0.0")]
[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)]
[assembly: AddinDescription("Region warmup: terrain touch, limited asset pre-touch, prime script VM, optional deep warmup tick.")]
[assembly: AddinAuthor("Christopher Händler")]
namespace OpenSimRegionWarmupHealthGuard.Modules
{
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "RegionWarmup")]
public class RegionWarmupModule : INonSharedRegionModule
{
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private Scene _scene;
private bool _enabled;
// Config
private bool _warmupOnRegionLoaded = true;
private bool _preloadAssets = true;
private bool _primeScriptVm = true;
private bool _touchTerrain = true;
private int _deepWarmupLimit = 200;
private int _deepWarmupDelaySec = 30;
private Timer _deepTimer;
public string Name => "RegionWarmup";
public Type ReplaceableInterface => null;
public void Initialise(IConfigSource source)
{
var modules = source.Configs["Modules"];
if (modules == null) return;
var status = modules.GetString("RegionWarmup", string.Empty);
if (!string.Equals(status, "enabled", StringComparison.OrdinalIgnoreCase)) return;
var cfg = source.Configs["RegionWarmup"] ?? source.AddConfig("RegionWarmup");
_warmupOnRegionLoaded = cfg.GetBoolean("WarmupOnRegionLoaded", _warmupOnRegionLoaded);
_preloadAssets = cfg.GetBoolean("PreloadAssets", _preloadAssets);
_primeScriptVm = cfg.GetBoolean("PrimeScriptVM", _primeScriptVm);
_touchTerrain = cfg.GetBoolean("TouchTerrain", _touchTerrain);
_deepWarmupLimit = Math.Max(0, cfg.GetInt("DeepWarmupLimit", _deepWarmupLimit));
_deepWarmupDelaySec = Math.Max(0, cfg.GetInt("DeepWarmupDelaySec", _deepWarmupDelaySec));
_enabled = true;
Log.Info("[WARMUP] RegionWarmup enabled");
}
public void AddRegion(Scene scene)
{
if (!_enabled) return;
_scene = scene;
_scene.RegisterModuleInterface(this);
}
public void RegionLoaded(Scene scene)
{
if (!_enabled) return;
MainConsole.Instance.Commands.AddCommand("Region", false, "warmup",
"warmup",
"Warmup commands. Usage: warmup run|status",
HandleConsole);
if (_warmupOnRegionLoaded)
{
RunWarmup(tag: "startup");
}
if (_deepWarmupLimit > 0 && _deepWarmupDelaySec > 0)
{
_deepTimer = new Timer(_deepWarmupDelaySec * 1000) { AutoReset = false };
_deepTimer.Elapsed += (_, __) =>
{
try
{
DeepWarmup(_deepWarmupLimit);
}
catch (Exception e)
{
Log.Debug($"[WARMUP] deep warmup failed: {e.Message}");
}
};
_deepTimer.Start();
}
Log.Info($"[WARMUP] Ready for region '{_scene.RegionInfo.RegionName}'.");
}
public void RemoveRegion(Scene scene)
{
if (!_enabled) return;
try { _deepTimer?.Stop(); _deepTimer?.Dispose(); } catch { }
_deepTimer = null;
_scene.UnregisterModuleInterface(this);
_scene = null;
}
public void Close()
{
try { _deepTimer?.Stop(); _deepTimer?.Dispose(); } catch { }
_deepTimer = null;
}
private void HandleConsole(string module, string[] args)
{
if (_scene == null) { MainConsole.Instance.Output("[WARMUP] No active scene."); return; }
if (args.Length < 2)
{
MainConsole.Instance.Output("warmup status - show config");
MainConsole.Instance.Output("warmup run - run warmup now");
return;
}
switch (args[1].ToLowerInvariant())
{
case "status":
MainConsole.Instance.Output($"[WARMUP] OnLoad={_warmupOnRegionLoaded} Terrain={_touchTerrain} PreloadAssets={_preloadAssets} PrimeVM={_primeScriptVm} DeepLimit={_deepWarmupLimit} DeepDelay={_deepWarmupDelaySec}s");
break;
case "run":
RunWarmup(tag: "manual");
MainConsole.Instance.Output("[WARMUP] Warmup completed.");
break;
default:
MainConsole.Instance.Output("warmup status|run");
break;
}
}
private void RunWarmup(string tag)
{
if (_touchTerrain)
{
try
{
var t = _scene.TerrainChannel;
var x = _scene.RegionInfo.RegionSizeX / 2;
var y = _scene.RegionInfo.RegionSizeY / 2;
var h = t.GetNormalizedGroundHeight(x, y);
Log.Debug($"[WARMUP] terrain touch ok (h={h:0.00})");
}
catch (Exception e) { Log.Debug($"[WARMUP] terrain touch skipped: {e.Message}"); }
}
if (_preloadAssets)
{
try
{
foreach (var so in _scene.GetSceneObjectGroups().Take(50))
{
var _ = so.Parts?.Length;
foreach (var p in so.Parts)
_ = p.Inventory?.GetInventoryItems()?.Count;
}
Log.Debug("[WARMUP] limited asset pre-touch ok");
}
catch (Exception e) { Log.Debug($"[WARMUP] pre-touch skipped: {e.Message}"); }
}
if (_primeScriptVm)
{
try
{
var engine = _scene.RequestModuleInterface<IScriptModule>();
if (engine != null)
{
int cnt = engine.GetScriptCount();
Log.Debug($"[WARMUP] script VM primed (scripts={cnt})");
}
}
catch (Exception e) { Log.Debug($"[WARMUP] script VM prime skipped: {e.Message}"); }
}
Log.Info($"[WARMUP] Warmup done ({tag})");
}
private void DeepWarmup(int limit)
{
try
{
int scanned = 0;
foreach (var so in _scene.GetSceneObjectGroups())
{
var parts = so.Parts;
if (parts == null) continue;
foreach (var p in parts)
{
var _ = p.Inventory?.GetInventoryItems()?.Count;
}
scanned++;
if (scanned >= limit) break;
}
Log.Info($"[WARMUP] deep warmup scanned objects={scanned}/{limit}");
}
catch (Exception e)
{
Log.Debug($"[WARMUP] deep warmup error: {e.Message}");
}
}
}
}