Browse Source

initial implementation of batching

Scrublord1336 6 years ago
parent
commit
ec45123c8c

+ 137 - 49
src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs

@@ -85,6 +85,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       private bool _isInTranslatedMode = true;
       private bool _hooksEnabled = true;
+      private bool _batchLogicHasFailed = false;
 
       public void Initialize()
       {
@@ -181,7 +182,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
          catch( Exception e )
          {
-            Logger.Current.Error( e, "An error occurred while saving translations to disk."  );
+            Logger.Current.Error( e, "An error occurred while saving translations to disk." );
          }
       }
 
@@ -727,78 +728,165 @@ namespace XUnity.AutoTranslator.Plugin.Core
       {
          if( _endpoint == null ) return;
 
-         foreach( var kvp in _unstartedJobs )
+         if( _endpoint.SupportsLineSplitting && !_batchLogicHasFailed )
          {
-            if( _endpoint.IsBusy ) break;
+            while( _unstartedJobs.Count > 0 )
+            {
+               if( _endpoint.IsBusy ) break;
 
-            var key = kvp.Key;
-            var job = kvp.Value;
-            _kickedOff.Add( key );
+               var kvps = _unstartedJobs.Take( 20 ).ToList();
+               var batch = new TranslationBatch();
+               bool addedAny = false;
 
-            // lets see if the text should still be translated before kicking anything off
-            if( !job.AnyComponentsStillHasOriginalUntranslatedText() ) continue;
+               foreach( var kvp in kvps )
+               {
+                  var key = kvp.Key;
+                  var job = kvp.Value;
+                  _kickedOff.Add( key );
 
-            StartCoroutine( _endpoint.Translate( job.Keys.GetDictionaryLookupKey(), Settings.FromLanguage, Settings.Language, translatedText =>
-            {
-               Settings.TranslationCount++;
+                  batch.Add( job );
 
-               if( !Settings.IsShutdown )
-               {
-                  if( Settings.TranslationCount > Settings.MaxTranslationsBeforeShutdown )
-                  {
-                     Settings.IsShutdown = true;
-                     Logger.Current.Error( $"Maximum translations ({Settings.MaxTranslationsBeforeShutdown}) per session reached. Shutting plugin down." );
-                  }
-               }
+                  if( !job.AnyComponentsStillHasOriginalUntranslatedText() ) continue;
 
-               _consecutiveErrors = 0;
+                  addedAny = true;
+               }
 
-               if( Settings.ForceSplitTextAfterCharacters > 0 )
+               if( addedAny )
                {
-                  translatedText = translatedText.SplitToLines( Settings.ForceSplitTextAfterCharacters, '\n', ' ', ' ' );
+                  StartCoroutine( _endpoint.Translate( batch.GetFullTranslationKey(), Settings.FromLanguage, Settings.Language, translatedText => OnBatchTranslationCompleted( batch, translatedText ),
+                  () => OnTranslationFailed() ) );
                }
+            }
+         }
+         else
+         {
+            foreach( var kvp in _unstartedJobs )
+            {
+               if( _endpoint.IsBusy ) break;
+
+               var key = kvp.Key;
+               var job = kvp.Value;
+               _kickedOff.Add( key );
+
+               // lets see if the text should still be translated before kicking anything off
+               if( !job.AnyComponentsStillHasOriginalUntranslatedText() ) continue;
+
+               StartCoroutine( _endpoint.Translate( job.Keys.GetDictionaryLookupKey(), Settings.FromLanguage, Settings.Language, translatedText => OnSingleTranslationCompleted( job, translatedText ),
+               () => OnTranslationFailed() ) );
+            }
+         }
+
+         for( int i = 0 ; i < _kickedOff.Count ; i++ )
+         {
+            _unstartedJobs.Remove( _kickedOff[ i ] );
+         }
+
+         _kickedOff.Clear();
+      }
 
-               job.TranslatedText = job.Keys.RepairTemplate( translatedText );
+      public void OnBatchTranslationCompleted( TranslationBatch batch, string translatedTextBatch )
+      {
+         Settings.TranslationCount++;
 
+         if( !Settings.IsShutdown )
+         {
+            if( Settings.TranslationCount > Settings.MaxTranslationsBeforeShutdown )
+            {
+               Settings.IsShutdown = true;
+               Logger.Current.Error( $"Maximum translations ({Settings.MaxTranslationsBeforeShutdown}) per session reached. Shutting plugin down." );
+            }
+         }
+
+         _consecutiveErrors = 0;
+
+         var succeeded = batch.MatchWithTranslations( translatedTextBatch );
+         if( succeeded )
+         {
+            foreach( var tracker in batch.Trackers )
+            {
+               var job = tracker.Job;
+               var translatedText = tracker.RawTranslatedText;
                if( !string.IsNullOrEmpty( translatedText ) )
                {
-                  QueueNewTranslationForDisk( job.Keys, translatedText );
+                  if( Settings.ForceSplitTextAfterCharacters > 0 )
+                  {
+                     translatedText = translatedText.SplitToLines( Settings.ForceSplitTextAfterCharacters, '\n', ' ', ' ' );
+                  }
+                  job.TranslatedText = job.Keys.RepairTemplate( translatedText );
 
+                  QueueNewTranslationForDisk( job.Keys, translatedText );
                   _completedJobs.Add( job );
                }
-            },
-            () =>
+            }
+         }
+         else
+         {
+            // might as well re-add all translation jobs, and never do this again!
+            _batchLogicHasFailed = true;
+            foreach( var tracker in batch.Trackers )
             {
-               _consecutiveErrors++;
-
-               if( !Settings.IsShutdown )
+               var key = tracker.Job.Keys.GetDictionaryLookupKey();
+               if( !_unstartedJobs.ContainsKey( key ) )
                {
-                  if( _consecutiveErrors > Settings.MaxErrors )
-                  {
-                     if( _endpoint.ShouldGetSecondChanceAfterFailure() )
-                     {
-                        Logger.Current.Warn( $"More than {Settings.MaxErrors} consecutive errors occurred. Entering fallback mode." );
-                        _consecutiveErrors = 0;
-                     }
-                     else
-                     {
-                        Settings.IsShutdown = true;
-                        Logger.Current.Error( $"More than {Settings.MaxErrors} consecutive errors occurred. Shutting down plugin." );
-
-                        _unstartedJobs.Clear();
-                        _completedJobs.Clear();
-                     }
-                  }
+                  _unstartedJobs[ key ] = tracker.Job;
                }
-            } ) );
+            }
+
+            Logger.Current.Error( "A batch operation failed. Disabling batching and restarting failed jobs." );
          }
+      }
 
-         for( int i = 0 ; i < _kickedOff.Count ; i++ )
+      private void OnSingleTranslationCompleted( TranslationJob job, string translatedText )
+      {
+         Settings.TranslationCount++;
+
+         if( !Settings.IsShutdown )
          {
-            _unstartedJobs.Remove( _kickedOff[ i ] );
+            if( Settings.TranslationCount > Settings.MaxTranslationsBeforeShutdown )
+            {
+               Settings.IsShutdown = true;
+               Logger.Current.Error( $"Maximum translations ({Settings.MaxTranslationsBeforeShutdown}) per session reached. Shutting plugin down." );
+            }
          }
 
-         _kickedOff.Clear();
+         _consecutiveErrors = 0;
+
+         if( !string.IsNullOrEmpty( translatedText ) )
+         {
+            if( Settings.ForceSplitTextAfterCharacters > 0 )
+            {
+               translatedText = translatedText.SplitToLines( Settings.ForceSplitTextAfterCharacters, '\n', ' ', ' ' );
+            }
+            job.TranslatedText = job.Keys.RepairTemplate( translatedText );
+
+            QueueNewTranslationForDisk( job.Keys, translatedText );
+            _completedJobs.Add( job );
+         }
+      }
+
+      private void OnTranslationFailed()
+      {
+         _consecutiveErrors++;
+
+         if( !Settings.IsShutdown )
+         {
+            if( _consecutiveErrors > Settings.MaxErrors )
+            {
+               if( _endpoint.ShouldGetSecondChanceAfterFailure() )
+               {
+                  Logger.Current.Warn( $"More than {Settings.MaxErrors} consecutive errors occurred. Entering fallback mode." );
+                  _consecutiveErrors = 0;
+               }
+               else
+               {
+                  Settings.IsShutdown = true;
+                  Logger.Current.Error( $"More than {Settings.MaxErrors} consecutive errors occurred. Shutting down plugin." );
+
+                  _unstartedJobs.Clear();
+                  _completedJobs.Clear();
+               }
+            }
+         }
       }
 
       private void FinishTranslations()

+ 85 - 1
src/XUnity.AutoTranslator.Plugin.Core/IKnownEndpoint.cs

@@ -1,8 +1,11 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using System.Text;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
+using XUnity.AutoTranslator.Plugin.Core.Extensions;
 
 namespace XUnity.AutoTranslator.Plugin.Core
 {
@@ -13,7 +16,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
       /// in an async fashion.
       /// </summary>
       IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action failure );
-      
+
       /// <summary>
       /// Gets a boolean indicating if we are allowed to call "Translate".
       /// </summary>
@@ -30,5 +33,86 @@ namespace XUnity.AutoTranslator.Plugin.Core
       /// "Update" game loop method.
       /// </summary>
       void OnUpdate();
+
+      bool SupportsLineSplitting { get; }
+   }
+
+   public class TranslationBatch
+   {
+      public TranslationBatch()
+      {
+         Trackers = new List<TranslationLineTracker>();
+      }
+
+      public List<TranslationLineTracker> Trackers { get; private set; }
+
+      public int TotalLinesCount { get; set; }
+
+      public void Add( TranslationJob job )
+      {
+         var lines = new TranslationLineTracker( job );
+         Trackers.Add( lines );
+         TotalLinesCount += lines.LinesCount;
+      }
+
+      public bool MatchWithTranslations( string allTranslations )
+      {
+         var lines = allTranslations.Split( '\n' );
+
+         if( lines.Length != TotalLinesCount ) return false;
+
+         int current = 0;
+         foreach( var tracker in Trackers )
+         {
+            var builder = new StringBuilder( 32 );
+            for( int i = 0 ; i < tracker.LinesCount ; i++ )
+            {
+               var translation = lines[ current++ ];
+               builder.Append( translation );
+
+               // ADD NEW LINE IF NEEDED
+               if( !( i == tracker.LinesCount - 1 ) ) // if not last line
+               {
+                  builder.Append( '\n' );
+               }
+            }
+            var fullTranslation = builder.ToString();
+
+            tracker.RawTranslatedText = fullTranslation;
+         }
+
+         return true;
+      }
+
+      public string GetFullTranslationKey()
+      {
+         var builder = new StringBuilder();
+         for( int i = 0 ; i < Trackers.Count ; i++ )
+         {
+            var tracker = Trackers[ i ];
+            builder.Append( tracker.Job.Keys.GetDictionaryLookupKey() );
+
+            if( !( i == Trackers.Count - 1 ) )
+            {
+               builder.Append( '\n' );
+            }
+         }
+         return builder.ToString();
+      }
+   }
+
+   public class TranslationLineTracker
+   {
+      public TranslationLineTracker( TranslationJob job )
+      {
+         Job = job;
+         LinesCount = job.Keys.GetDictionaryLookupKey().Count( c => c == '\n' ) + 1;
+      }
+
+      public string RawTranslatedText { get; set; }
+
+      public TranslationJob Job { get; private set; }
+
+      public int LinesCount { get; private set; }
    }
 }

+ 2 - 0
src/XUnity.AutoTranslator.Plugin.Core/Web/GoogleTranslateEndpoint.cs

@@ -37,6 +37,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          SetupServicePoints( "https://translate.googleapis.com", "https://translate.google.com" );
       }
 
+      public override bool SupportsLineSplitting => true;
+
       public override void ApplyHeaders( WebHeaderCollection headers )
       {
          headers[ HttpRequestHeader.UserAgent ] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36";

+ 8 - 0
src/XUnity.AutoTranslator.Plugin.Core/Web/KnownHttpEndpoint.cs

@@ -21,6 +21,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
 
       public bool IsBusy => _isBusy;
 
+      public virtual bool SupportsLineSplitting
+      {
+         get
+         {
+            return false;
+         }
+      }
+
       protected void SetupServicePoints( params string[] endpoints )
       {
          _servicePoints = new ServicePoint[ endpoints.Length ];

+ 8 - 0
src/XUnity.AutoTranslator.Plugin.Core/Web/KnownWwwEndpoint.cs

@@ -21,6 +21,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
 
       public bool IsBusy => _isBusy;
 
+      public virtual bool SupportsLineSplitting
+      {
+         get
+         {
+            return false;
+         }
+      }
+
       public IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action failure )
       {
          _isBusy = true;