388 lines
13 KiB
C#
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
|
|
}
|
|
}
|