JD2022-TU1/main/tools/framework/JD.Timeline/Plugin/TapesReplicator.cs

388 lines
13 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using JD.Application.Utils;
using JD.Collections;
using JD.Collections.Extensions;
using JD.Controls;
using JD.Core.Collections;
using JD.Interfaces.Remoting;
using JD.Kernel.StaticHelpers;
using JD.Remoting;
using JD.Timeline.Document;
using JD.Timeline.Model;
using JD.Serialization;
using CollectionChangeAction = JD.Collections.CollectionChangeAction;
using CollectionExtensions = JD.Core.Collections.CollectionExtensions;
using PropertyChangedEventArgs = System.ComponentModel.PropertyChangedEventArgs;
namespace JD.Timeline.Plugin
{
/// <summary>
/// Responsible of synchronizing Tapes between the editor and the engine.
/// </summary>
public class TapesReplicator
{
#region Variables
private readonly TimelinePlugin _plugin;
private readonly TapeCollection _tapes;
private readonly HashSet<Clip> _clipsToRemove = new HashSet<Clip>();
private readonly HashSet<Clip> _clipsToAdd = new HashSet<Clip>();
private readonly HashSet<Clip> _clipsToUpdate = new HashSet<Clip>();
private ConcurrentQueue<Tape> _tapesToAdd = new ConcurrentQueue<Tape>();
private readonly HashSet<Tape> _tapesToUpdate = new HashSet<Tape>();
private readonly RefreshTask _sendUpdatesToEngineTask;
#endregion
#region Constructor
public TapesReplicator(TimelinePlugin plugin, TapeCollection tapes)
{
Throw.IfNull(plugin, "plugin");
Throw.IfNull(tapes, "tapes");
_tapes = tapes;
_plugin = plugin;
_plugin.RegisterHandler(CommandNames.SendTape, HandleSendTape);
_plugin.RegisterHandler(CommandNames.CloseAllTapes, HandleCloseAllTapes);
_plugin.RegisterHandler(CommandNames.EditTape, HandleEditTape);
_plugin.RegisterHandler(CommandNames.TapeNotFound, HandleTapeNotFound);
_plugin.RegisterHandler(CommandNames.DisableClockEdition, HandleDisableClockEdition);
_plugin.BatchOperation.EndScope += batchOperation_EndScope;
_plugin.ConnectedChanged += plugin_ConnectedChanged;
_plugin.World.WaveChanged += player_WaveChanged;
_tapes.CollectionChanged += tapes_CollectionChanged;
_sendUpdatesToEngineTask = new RefreshTask(sendUpdatesToEngineTask_Callback);
}
#endregion
#region Properties
public TapeCollection Tapes
{
get { return _tapes; }
}
#endregion
#region Methods
/// <summary>
/// When requested, makes sure that the current queue is dumped.
/// </summary>
public void SendPendingUpdates()
{
if (_clipsToRemove.Any())
{
CollectionExtensions.RemoveAll(_clipsToRemove, clip => _tapesToUpdate.Contains(clip.Tape));
_plugin.SendRemoveClips(_clipsToRemove);
_clipsToRemove.Clear();
}
if (_clipsToAdd.Any())
{
CollectionExtensions.RemoveAll(_clipsToAdd, clip => _tapesToUpdate.Contains(clip.Tape));
_plugin.SendAddClips(_clipsToAdd);
_clipsToAdd.ForEach(RequestReferredTape);
_clipsToAdd.Clear();
}
if (_clipsToUpdate.Any())
{
CollectionExtensions.RemoveAll(_clipsToUpdate, clip => _tapesToUpdate.Contains(clip.Tape));
_plugin.SendUpdateClips(_clipsToUpdate);
_clipsToUpdate.Clear();
}
foreach (var tape in _tapesToUpdate)
{
_plugin.UpdateTape(tape);
}
_tapesToUpdate.Clear();
}
private void RegisterTape(Tape tape)
{
tape.PropertyChanged += tape_PropertyChanged;
tape.Tracks.CollectionChanged += tracks_CollectionChanged;
tape.Tracks.ItemChanged += tracks_ItemChanged;
tape.Clips.CollectionChanged += clips_CollectionChanged;
tape.Clips.ItemChanged += clips_ItemChanged;
UpdateWave(tape);
}
private void UnregisterTape(Tape tape)
{
tape.PropertyChanged -= tape_PropertyChanged;
tape.Tracks.CollectionChanged -= tracks_CollectionChanged;
tape.Tracks.ItemChanged -= tracks_ItemChanged;
tape.Clips.CollectionChanged -= clips_CollectionChanged;
}
private void CloseAllTapes()
{
_tapesToAdd = new ConcurrentQueue<Tape>();
RemoveUnreferencedTapes(new Set<Tape>());
}
private void RemoveUnreferencedTapes(Set<Tape> referencedTapes)
{
foreach (var tape in _tapes.ToArray())
{
if (!referencedTapes.Contains(tape))
{
_tapes.Remove(tape);
tape.CloseDocument();
}
}
}
private void UpdateWave(Tape tape)
{
if (_plugin.World.CurrentWave == null)
return;
using (tape.ReplicationOperation().Begin())
{
tape.SetWave(_plugin.World.CurrentWave);
}
}
#endregion
#region Command Handlers
private void HandleTapeNotFound(IBlob blob)
{
string objectId = "";
blob.Extract(out objectId);
Tape tape;
if (_tapes.TryGetValue(objectId, out tape))
{
//tape.ReplicationState = ReplicationState.NotFoundInEngine;
}
}
private void HandleCloseAllTapes(IBlob blob)
{
CloseAllTapes();
}
private void HandleDisableClockEdition(IBlob blob)
{
using (_tapes.ReplicationOperation.Begin())
{
string objectId = "";
blob.Extract(out objectId);
Tape tape = _tapes.GetOrCreate(objectId);
tape.DisableClockEdition = true;
}
}
private void HandleSendTape(IBlob blob)
{
using (_tapes.ReplicationOperation.Begin())
{
string tapeId = blob.ExtractString();
Tape tape = _tapes.GetOrCreate(tapeId);
try
{
using (tape.UpdateOperation().Begin())
{
tape.OnBeforeLoading();
blob.ExtractObject(tape);
tape.Id = tapeId;
tape.OnAfterLoading();
}
}
catch (Exception ex)
{
// This may happen when the TRec is not synchronized with the current running engine.
// we just send a nice message in this case.
tape.LoadingError = new Exception("Invalid data was sent from the running game. This tape is invalid. Make sure to use the engine version matching the current TRec version. \n\nDetails: " + ex.Message);
return;
}
tape.Clips.ForEach(RequestReferredTape);
}
}
private void HandleEditTape(IBlob blob)
{
// Had bloomberg on this "Race condition" where the application is closing
// while it receives this event
if (TeaBox.ObjectModel.Application.Main.IsDisposed || TeaBox.ObjectModel.Application.Main.IsQuitting)
{
return;
}
string objectId = "";
blob.Extract(out objectId);
Tape tape = _tapes.GetOrCreate(objectId);
_tapesToAdd.Enqueue(tape);
TeaBox.ObjectModel.Application.Main.Dispatcher.DispatchAsync(() =>
{
Tape result;
if (_tapesToAdd.TryDequeue(out result))
{
result.OpenDocument();
}
});
}
public void RequestReferredTape(Clip item)
{
var tapeReference = item as TapeReferenceClip;
if (tapeReference != null)
{
tapeReference.ReferredTape = RequestTape(tapeReference.TapeId);
}
}
private Tape RequestTape(string tapeId)
{
Tape tape;
if (!_tapes.TryGetValue(tapeId, out tape))
{
_plugin.SendGetTape(tapeId);
return _tapes.GetOrCreate(tapeId);
}
return tape;
}
private Tape FindTape(NotifyCollectionChangedEventArgs e)
{
return e.NewItems<Clip>().Concat(e.OldItems<Clip>()).Select(clip => clip.Tape).FirstOrDefault();
}
private bool IsReplicating(Tape tape)
{
return _tapes.ReplicationOperation.InScope || (tape != null && tape.ReplicationOperation().InScope);
}
#endregion
#region Event Handlers
void plugin_ConnectedChanged(object sender, IPlugin e)
{
if (!_plugin.IsConnected)
{
TeaBox.ObjectModel.Application.Main.Dispatcher.Dispatch(CloseAllTapes);
}
}
private void clips_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Tape tape = FindTape(e);
if (Tapes.ReplicationOperation.InScope || tape == null || tape.ReplicationOperation().InScope)
{
return;
}
//We add clips that have been just added
_clipsToRemove.AddRange(e.OldItems<Clip>());
//We remove clips that have been just added
_clipsToAdd.AddRange(e.NewItems<Clip>());
//We don't update clips that should be added or removed
var clipsToNotUpdate = _clipsToAdd.Union(_clipsToRemove).ToList();
_clipsToUpdate.RemoveRange(clipsToNotUpdate);
//We remove clips that can't be added and removed at the same time
var clipsToCancel = _clipsToAdd.Intersect(_clipsToRemove).ToList();
_clipsToAdd.RemoveRange(clipsToCancel);
_clipsToRemove.RemoveRange(clipsToCancel);
_sendUpdatesToEngineTask.Refresh();
}
private void clips_ItemChanged(object sender, ItemChangedEventArgs<Clip> e)
{
Tape tape = e.Item.Tape;
if (Tapes.ReplicationOperation.InScope || tape == null || tape.ReplicationOperation().InScope || e.Item.IsComputedProperty(e.PropertyName))
{
return;
}
_clipsToUpdate.Add(e.Item);
_sendUpdatesToEngineTask.Refresh();
}
private void tracks_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Tape tape = ((UAFList<TapeTrack>)sender).Select(x => x.Tape).FirstOrDefault();
if (tape != null && !IsReplicating(tape))
{
_tapesToUpdate.Add(tape);
}
}
private void tracks_ItemChanged(object sender, ItemChangedEventArgs<TapeTrack> e)
{
if (e.Item.Tape != null && !IsReplicating(e.Item.Tape))
{
_tapesToUpdate.Add(e.Item.Tape);
}
}
void tapes_CollectionChanged(object sender, CollectionChangedEventArgs<Tape> e)
{
e.Synchronize(_tapes, RegisterTape, UnregisterTape);
if (e.Action == CollectionChangeAction.ItemAdded)
{
_plugin.SendGetWave();
}
}
private void tape_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var tape = (Tape)sender;
if (!IsReplicating(tape) &&
(e.PropertyName == "TapeClock"
|| e.PropertyName == "TapeBarCount"
|| e.PropertyName == "FreeResourcesAfterPlay"
|| e.PropertyName == "SoundwichEvent")
)
{
_tapesToUpdate.Add(tape);
_sendUpdatesToEngineTask.Refresh();
}
}
private void batchOperation_EndScope(object sender, EventArgs e)
{
_sendUpdatesToEngineTask.Refresh();
}
private void player_WaveChanged(object sender, EventArgs e)
{
_tapes.ForEach(UpdateWave);
}
private void sendUpdatesToEngineTask_Callback()
{
if (_plugin.BatchOperation.InScope)
{
return;
}
SendPendingUpdates();
}
#endregion
}
}