actually start to bring structure into the project
make it resilent against restarts/reloads remove all user interaction for resource gathering compute hashes on first time file resolving and on updates of said file on resolving
This commit is contained in:
@@ -18,7 +18,6 @@ using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Data;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Glamourer.Customization;
|
||||
using System.Text;
|
||||
using Penumbra.GameData.Enums;
|
||||
using System;
|
||||
@@ -31,106 +30,133 @@ using System.Text.Unicode;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System.Reflection;
|
||||
using MareSynchronos.Managers;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
|
||||
namespace SamplePlugin
|
||||
namespace MareSynchronos
|
||||
{
|
||||
public sealed class Plugin : IDalamudPlugin
|
||||
{
|
||||
public string Name => "Mare Synchronos";
|
||||
|
||||
private const string commandName = "/mare";
|
||||
private readonly Framework framework;
|
||||
private readonly ObjectTable objectTable;
|
||||
private readonly ClientState clientState;
|
||||
private readonly GameGui gameGui;
|
||||
|
||||
private DalamudPluginInterface PluginInterface { get; init; }
|
||||
private CommandManager CommandManager { get; init; }
|
||||
private Configuration Configuration { get; init; }
|
||||
private DalamudPluginInterface pluginInterface { get; init; }
|
||||
private CommandManager commandManager { get; init; }
|
||||
private Configuration configuration { get; init; }
|
||||
private PluginUI PluginUi { get; init; }
|
||||
private FileCacheFactory FileCacheFactory { get; init; }
|
||||
private DrawHooks drawHooks;
|
||||
private IpcManager ipcManager;
|
||||
|
||||
private CancellationTokenSource cts;
|
||||
private IPlayerWatcher playerWatch;
|
||||
|
||||
public Plugin(
|
||||
[RequiredVersion("1.0")] DalamudPluginInterface pluginInterface,
|
||||
[RequiredVersion("1.0")] CommandManager commandManager,
|
||||
Framework framework, ObjectTable objectTable, ClientState clientState, DataManager dataManager, GameGui gameGui)
|
||||
public Plugin(DalamudPluginInterface pluginInterface, CommandManager commandManager,
|
||||
Framework framework, ObjectTable objectTable, ClientState clientState, GameGui gameGui)
|
||||
{
|
||||
this.PluginInterface = pluginInterface;
|
||||
this.CommandManager = commandManager;
|
||||
this.pluginInterface = pluginInterface;
|
||||
this.commandManager = commandManager;
|
||||
this.framework = framework;
|
||||
this.objectTable = objectTable;
|
||||
this.clientState = clientState;
|
||||
this.Configuration = this.PluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
|
||||
this.Configuration.Initialize(this.PluginInterface);
|
||||
this.gameGui = gameGui;
|
||||
configuration = this.pluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
|
||||
configuration.Initialize(this.pluginInterface);
|
||||
|
||||
// you might normally want to embed resources and load them from the manifest stream
|
||||
this.PluginUi = new PluginUI(this.Configuration);
|
||||
this.PluginUi = new PluginUI(this.configuration);
|
||||
|
||||
this.CommandManager.AddHandler(commandName, new CommandInfo(OnCommand)
|
||||
this.commandManager.AddHandler(commandName, new CommandInfo(OnCommand)
|
||||
{
|
||||
HelpMessage = "pass 'scan' to initialize or rescan files into the database"
|
||||
});
|
||||
|
||||
FileCacheFactory = new FileCacheFactory();
|
||||
FileCacheContext db = new FileCacheContext();
|
||||
db.Dispose();
|
||||
|
||||
this.PluginInterface.UiBuilder.Draw += DrawUI;
|
||||
this.PluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI;
|
||||
clientState.Login += ClientState_Login;
|
||||
clientState.Logout += ClientState_Logout;
|
||||
|
||||
playerWatch = PlayerWatchFactory.Create(framework, clientState, objectTable);
|
||||
drawHooks = new DrawHooks(pluginInterface, clientState, objectTable, new FileReplacementFactory(pluginInterface, clientState), gameGui);
|
||||
if (clientState.IsLoggedIn)
|
||||
{
|
||||
ClientState_Login(null, null!);
|
||||
}
|
||||
|
||||
this.pluginInterface.UiBuilder.Draw += DrawUI;
|
||||
this.pluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI;
|
||||
}
|
||||
|
||||
private void IpcManager_IpcManagerInitialized(object? sender, EventArgs e)
|
||||
{
|
||||
PluginLog.Debug("IPC Manager initialized event");
|
||||
ipcManager.IpcManagerInitialized -= IpcManager_IpcManagerInitialized;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (clientState.LocalPlayer == null)
|
||||
{
|
||||
await Task.Delay(500);
|
||||
}
|
||||
drawHooks.StartHooks();
|
||||
ipcManager.PenumbraRedraw(clientState.LocalPlayer!.Name.ToString());
|
||||
});
|
||||
}
|
||||
|
||||
private void ClientState_Logout(object? sender, EventArgs e)
|
||||
{
|
||||
PluginLog.Debug("Client logout");
|
||||
drawHooks.PlayerLoadEvent -= DrawHooks_PlayerLoadEvent;
|
||||
ipcManager.Dispose();
|
||||
drawHooks.Dispose();
|
||||
ipcManager = null!;
|
||||
drawHooks = null!;
|
||||
}
|
||||
|
||||
private void ClientState_Login(object? sender, EventArgs e)
|
||||
{
|
||||
PluginLog.Debug("Client login");
|
||||
ipcManager = new IpcManager(pluginInterface);
|
||||
drawHooks = new DrawHooks(pluginInterface, clientState, objectTable, new FileReplacementFactory(ipcManager, clientState), gameGui);
|
||||
ipcManager.IpcManagerInitialized += IpcManager_IpcManagerInitialized;
|
||||
ipcManager.Initialize();
|
||||
drawHooks.PlayerLoadEvent += DrawHooks_PlayerLoadEvent;
|
||||
}
|
||||
|
||||
private Task drawHookTask;
|
||||
|
||||
private unsafe void DrawHooks_PlayerLoadEvent(object? sender, EventArgs e)
|
||||
{
|
||||
if (sender == null) return;
|
||||
if (drawHookTask != null && !drawHookTask.IsCompleted) return;
|
||||
|
||||
var obj = (GameObject*)(IntPtr)sender;
|
||||
drawHookTask = Task.Run(() =>
|
||||
{
|
||||
while ((obj->RenderFlags & 0b100000000000) == 0b100000000000) // 0b100000000000 is "still rendering" or something
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
|
||||
// we should recalculate cache here
|
||||
// probably needs a different method
|
||||
// at that point we will also have to send data to the api
|
||||
_ = drawHooks.BuildCharacterCache();
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.PluginUi.Dispose();
|
||||
this.CommandManager.RemoveHandler(commandName);
|
||||
playerWatch.PlayerChanged -= PlayerWatch_PlayerChanged;
|
||||
playerWatch.RemovePlayerFromWatch("Ilya Zhelmo");
|
||||
drawHooks.Dispose();
|
||||
this.PluginUi?.Dispose();
|
||||
this.commandManager.RemoveHandler(commandName);
|
||||
drawHooks.PlayerLoadEvent -= DrawHooks_PlayerLoadEvent;
|
||||
clientState.Login -= ClientState_Login;
|
||||
clientState.Logout -= ClientState_Logout;
|
||||
ipcManager?.Dispose();
|
||||
drawHooks?.Dispose();
|
||||
}
|
||||
|
||||
private void OnCommand(string command, string args)
|
||||
{
|
||||
if (args == "stop")
|
||||
{
|
||||
cts?.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if(args == "playerdata")
|
||||
{
|
||||
PluginLog.Debug(PluginInterface.GetIpcSubscriber<string>("Glamourer.GetCharacterCustomization").InvokeFunc());
|
||||
}
|
||||
|
||||
if(args == "applyglam")
|
||||
{
|
||||
PluginInterface.GetIpcSubscriber<string, string, object>("Glamourer.ApplyCharacterCustomization")
|
||||
.InvokeAction("Ah3/DwQBAR4IBHOABIOceTIIApkDAgADQmQBZJqepQZlAAEAAAAAAAAAAACcEwEAyxcBbrAXAUnKFwJIuBcBBkYAAQBIAAEANQABADUAAQACAAQAAQAAAIA/Eg==", "Ilya Zhelmo");
|
||||
}
|
||||
|
||||
if (args == "scan")
|
||||
{
|
||||
cts = new CancellationTokenSource();
|
||||
|
||||
Task.Run(() => StartScan(), cts.Token);
|
||||
}
|
||||
|
||||
if (args == "watch")
|
||||
{
|
||||
playerWatch.AddPlayerToWatch("Ilya Zhelmo");
|
||||
playerWatch.PlayerChanged += PlayerWatch_PlayerChanged;
|
||||
}
|
||||
|
||||
if (args == "stopwatch")
|
||||
{
|
||||
playerWatch.PlayerChanged -= PlayerWatch_PlayerChanged;
|
||||
playerWatch.RemovePlayerFromWatch("Ilya Zhelmo");
|
||||
}
|
||||
|
||||
if (args == "hook")
|
||||
{
|
||||
drawHooks.StartHooks();
|
||||
}
|
||||
|
||||
if (args == "print")
|
||||
{
|
||||
var resources = drawHooks.PrintRequestedResources();
|
||||
@@ -139,8 +165,17 @@ namespace SamplePlugin
|
||||
if (args == "printjson")
|
||||
{
|
||||
var cache = drawHooks.BuildCharacterCache();
|
||||
var json = JsonConvert.SerializeObject(cache, Formatting.Indented);
|
||||
PluginLog.Debug(json);
|
||||
cache.SetGlamourerData(ipcManager.GlamourerGetCharacterCustomization()!);
|
||||
cache.JobId = clientState.LocalPlayer!.ClassJob.Id;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (!cache.IsReady)
|
||||
{
|
||||
await Task.Delay(50);
|
||||
}
|
||||
var json = JsonConvert.SerializeObject(cache, Formatting.Indented);
|
||||
PluginLog.Debug(json);
|
||||
});
|
||||
}
|
||||
|
||||
if (args == "createtestmod")
|
||||
@@ -149,7 +184,7 @@ namespace SamplePlugin
|
||||
{
|
||||
var playerName = clientState.LocalPlayer!.Name.ToString();
|
||||
var modName = $"Mare Synchronos Test Mod {playerName}";
|
||||
var modDirectory = PluginInterface.GetIpcSubscriber<string>("Penumbra.GetModDirectory").InvokeFunc();
|
||||
var modDirectory = ipcManager.PenumbraModDirectory()!;
|
||||
string modDirectoryPath = Path.Combine(modDirectory, modName);
|
||||
if (Directory.Exists(modDirectoryPath))
|
||||
{
|
||||
@@ -216,100 +251,14 @@ namespace SamplePlugin
|
||||
|
||||
private void PlayerWatch_PlayerChanged(Dalamud.Game.ClientState.Objects.Types.Character actor)
|
||||
{
|
||||
var equipment = playerWatch.UpdatePlayerWithoutEvent(actor);
|
||||
var customization = new CharacterCustomization(actor);
|
||||
//var equipment = playerWatch.UpdatePlayerWithoutEvent(actor);
|
||||
//var customization = new CharacterCustomization(actor);
|
||||
//DebugCustomization(customization);
|
||||
//PluginLog.Debug(customization.Gender.ToString());
|
||||
if (equipment != null)
|
||||
{
|
||||
PluginLog.Debug(equipment.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void StartScan()
|
||||
{
|
||||
Stopwatch st = Stopwatch.StartNew();
|
||||
|
||||
string penumbraDir = PluginInterface.GetIpcSubscriber<string>("Penumbra.GetModDirectory").InvokeFunc();
|
||||
PluginLog.Debug("Getting files from " + penumbraDir);
|
||||
ConcurrentDictionary<string, bool> charaFiles = new ConcurrentDictionary<string, bool>(
|
||||
Directory.GetFiles(penumbraDir, "*.*", SearchOption.AllDirectories)
|
||||
.Select(s => s.ToLowerInvariant())
|
||||
.Where(f => !f.EndsWith(".json"))
|
||||
.Where(f => f.Contains(@"\chara\"))
|
||||
.Select(p => new KeyValuePair<string, bool>(p, false)));
|
||||
int count = 0;
|
||||
using FileCacheContext db = new();
|
||||
var fileCaches = db.FileCaches.ToList();
|
||||
|
||||
var fileCachesToUpdate = new ConcurrentBag<FileCache>();
|
||||
var fileCachesToDelete = new ConcurrentBag<FileCache>();
|
||||
var fileCachesToAdd = new ConcurrentBag<FileCache>();
|
||||
|
||||
// scan files from database
|
||||
Parallel.ForEach(fileCaches, new ParallelOptions()
|
||||
{
|
||||
CancellationToken = cts.Token,
|
||||
MaxDegreeOfParallelism = 10
|
||||
},
|
||||
cache =>
|
||||
{
|
||||
count = Interlocked.Increment(ref count);
|
||||
PluginLog.Debug($"[{count}/{fileCaches.Count}] Checking: {cache.Filepath}");
|
||||
|
||||
if (!File.Exists(cache.Filepath))
|
||||
{
|
||||
PluginLog.Debug("File was not found anymore: " + cache.Filepath);
|
||||
fileCachesToDelete.Add(cache);
|
||||
}
|
||||
else
|
||||
{
|
||||
charaFiles[cache.Filepath] = true;
|
||||
|
||||
FileInfo fileInfo = new(cache.Filepath);
|
||||
if (fileInfo.LastWriteTimeUtc.Ticks != long.Parse(cache.LastModifiedDate))
|
||||
{
|
||||
PluginLog.Debug("File was modified since last time: " + cache.Filepath + "; " + cache.LastModifiedDate + " / " + fileInfo.LastWriteTimeUtc.Ticks);
|
||||
FileCacheFactory.UpdateFileCache(cache);
|
||||
fileCachesToUpdate.Add(cache);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// scan new files
|
||||
count = 0;
|
||||
Parallel.ForEach(charaFiles.Where(c => c.Value == false), new ParallelOptions()
|
||||
{
|
||||
CancellationToken = cts.Token,
|
||||
MaxDegreeOfParallelism = 10
|
||||
},
|
||||
file =>
|
||||
{
|
||||
count = Interlocked.Increment(ref count);
|
||||
PluginLog.Debug($"[{count}/{charaFiles.Count()}] Hashing: {file.Key}");
|
||||
|
||||
fileCachesToAdd.Add(FileCacheFactory.Create(file.Key));
|
||||
});
|
||||
|
||||
st.Stop();
|
||||
|
||||
if (cts.Token.IsCancellationRequested) return;
|
||||
|
||||
PluginLog.Debug("Scanning complete, total elapsed time: " + st.Elapsed.ToString());
|
||||
|
||||
if (fileCachesToAdd.Any() || fileCachesToUpdate.Any() || fileCachesToDelete.Any())
|
||||
{
|
||||
PluginLog.Debug("Writing files to database…");
|
||||
|
||||
db.FileCaches.AddRange(fileCachesToAdd);
|
||||
db.FileCaches.UpdateRange(fileCachesToUpdate);
|
||||
db.FileCaches.RemoveRange(fileCachesToDelete);
|
||||
|
||||
db.SaveChanges();
|
||||
PluginLog.Debug("Database has been written.");
|
||||
}
|
||||
|
||||
cts = new CancellationTokenSource();
|
||||
//if (equipment != null)
|
||||
//{
|
||||
// PluginLog.Debug(equipment.ToString());
|
||||
//}
|
||||
}
|
||||
|
||||
private void DrawUI()
|
||||
|
||||
Reference in New Issue
Block a user