|
@@ -14,10 +14,18 @@ using XUnity.AutoTranslator.Plugin.ExtProtocol;
|
|
|
namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.ExtProtocol
|
|
|
{
|
|
|
|
|
|
- public abstract class ExtProtocolEndpoint : ITranslateEndpoint, IDisposable
|
|
|
+ public abstract class ExtProtocolEndpoint : MonoBehaviour, ITranslateEndpoint, IDisposable
|
|
|
{
|
|
|
+ private readonly Dictionary<Guid, StreamReaderResult> _pendingRequests = new Dictionary<Guid, StreamReaderResult>();
|
|
|
+ private readonly object _sync = new object();
|
|
|
+
|
|
|
private bool _disposed = false;
|
|
|
private Process _process;
|
|
|
+ private Thread _thread;
|
|
|
+ private bool _startedThread;
|
|
|
+ private bool _initializing;
|
|
|
+ private bool _failed;
|
|
|
+
|
|
|
protected string _exePath;
|
|
|
protected string _arguments;
|
|
|
|
|
@@ -25,126 +33,211 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.ExtProtocol
|
|
|
|
|
|
public abstract string FriendlyName { get; }
|
|
|
|
|
|
+ public virtual int MaxConcurrency => 1;
|
|
|
+
|
|
|
public abstract void Initialize( IInitializationContext context );
|
|
|
|
|
|
- public int MaxConcurrency => 1;
|
|
|
+ private void EnsureInitialized()
|
|
|
+ {
|
|
|
+ if( !_startedThread )
|
|
|
+ {
|
|
|
+ _startedThread = true;
|
|
|
+ _initializing = true;
|
|
|
|
|
|
- public IEnumerator Translate( ITranslationContext context )
|
|
|
+ _thread = new Thread( ReaderLoop );
|
|
|
+ _thread.IsBackground = true;
|
|
|
+ _thread.Start();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ReaderLoop( object state )
|
|
|
{
|
|
|
- var result = new StreamReaderResult();
|
|
|
try
|
|
|
{
|
|
|
- ThreadPool.QueueUserWorkItem( state =>
|
|
|
+ if( _process == null )
|
|
|
{
|
|
|
- try
|
|
|
- {
|
|
|
- if( _process == null )
|
|
|
- {
|
|
|
- _process = new Process();
|
|
|
- _process.StartInfo.FileName = _exePath;
|
|
|
- _process.StartInfo.Arguments = _arguments;
|
|
|
- _process.EnableRaisingEvents = false;
|
|
|
- _process.StartInfo.UseShellExecute = false;
|
|
|
- _process.StartInfo.CreateNoWindow = true;
|
|
|
- _process.StartInfo.RedirectStandardInput = true;
|
|
|
- _process.StartInfo.RedirectStandardOutput = true;
|
|
|
- _process.StartInfo.RedirectStandardError = true;
|
|
|
- _process.Start();
|
|
|
-
|
|
|
- // wait a second...
|
|
|
- _process.WaitForExit( 2500 );
|
|
|
- }
|
|
|
-
|
|
|
- if( _process.HasExited )
|
|
|
- {
|
|
|
- result.SetCompleted( null, "The translation process exited. Likely due to invalid path to installation." );
|
|
|
- return;
|
|
|
- }
|
|
|
+ _process = new Process();
|
|
|
+ _process.StartInfo.FileName = _exePath;
|
|
|
+ _process.StartInfo.Arguments = _arguments;
|
|
|
+ _process.EnableRaisingEvents = false;
|
|
|
+ _process.StartInfo.UseShellExecute = false;
|
|
|
+ _process.StartInfo.CreateNoWindow = true;
|
|
|
+ _process.StartInfo.RedirectStandardInput = true;
|
|
|
+ _process.StartInfo.RedirectStandardOutput = true;
|
|
|
+ _process.StartInfo.RedirectStandardError = true;
|
|
|
+ _process.Start();
|
|
|
|
|
|
- var request = new TranslationRequest
|
|
|
- {
|
|
|
- Id = Guid.NewGuid(),
|
|
|
- SourceLanguage = context.SourceLanguage,
|
|
|
- DestinationLanguage = context.DestinationLanguage,
|
|
|
- UntranslatedText = context.UntranslatedText
|
|
|
- };
|
|
|
- var payload = ExtProtocolConvert.Encode( request );
|
|
|
- _process.StandardInput.WriteLine( payload );
|
|
|
-
|
|
|
- var returnedPayload = _process.StandardOutput.ReadLine();
|
|
|
- var response = ExtProtocolConvert.Decode( returnedPayload );
|
|
|
-
|
|
|
- HandleProtocolMessage( result, response );
|
|
|
- }
|
|
|
- catch( Exception e )
|
|
|
- {
|
|
|
- result.SetCompleted( null, e.Message );
|
|
|
- }
|
|
|
- } );
|
|
|
+ // wait a second...
|
|
|
+ _process.WaitForExit( 2500 );
|
|
|
+ }
|
|
|
|
|
|
- // yield-wait for completion
|
|
|
- if( Features.SupportsCustomYieldInstruction )
|
|
|
+ if( _process.HasExited )
|
|
|
{
|
|
|
- yield return result;
|
|
|
+ return;
|
|
|
}
|
|
|
- else
|
|
|
+ _initializing = false;
|
|
|
+
|
|
|
+ while( !_disposed )
|
|
|
{
|
|
|
- while( !result.IsCompleted )
|
|
|
- {
|
|
|
- yield return new WaitForSeconds( 0.2f );
|
|
|
- }
|
|
|
+ var returnedPayload = _process.StandardOutput.ReadLine();
|
|
|
+ var response = ExtProtocolConvert.Decode( returnedPayload );
|
|
|
+ HandleProtocolMessage( response );
|
|
|
}
|
|
|
+ }
|
|
|
+ catch( Exception e )
|
|
|
+ {
|
|
|
+ _failed = true;
|
|
|
+ _initializing = false;
|
|
|
|
|
|
- try
|
|
|
+ XuaLogger.Current.Error( e, "Error occurred while reading standard output from external process." );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void Update()
|
|
|
+ {
|
|
|
+ if( Time.frameCount % 30 == 0 )
|
|
|
+ {
|
|
|
+ lock( _sync )
|
|
|
{
|
|
|
- if( result.Succeeded )
|
|
|
+ var time = Time.realtimeSinceStartup;
|
|
|
+
|
|
|
+ List<Guid> idsToRemove = null;
|
|
|
+ foreach( var kvp in _pendingRequests )
|
|
|
{
|
|
|
- context.Complete( result.Result );
|
|
|
+ var elapsed = time - kvp.Value.StartTime;
|
|
|
+ if( elapsed > 60 )
|
|
|
+ {
|
|
|
+ if( idsToRemove == null )
|
|
|
+ {
|
|
|
+ idsToRemove = new List<Guid>();
|
|
|
+ }
|
|
|
+
|
|
|
+ idsToRemove.Add( kvp.Key );
|
|
|
+ kvp.Value.SetCompleted( null, "Request timed out." );
|
|
|
+ }
|
|
|
}
|
|
|
- else
|
|
|
+
|
|
|
+ if( idsToRemove != null )
|
|
|
{
|
|
|
- context.Fail( "Error occurred while retrieving translation." + Environment.NewLine + result.Error, null );
|
|
|
+ foreach( var id in idsToRemove )
|
|
|
+ {
|
|
|
+ _pendingRequests.Remove( id );
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- catch( Exception e )
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public IEnumerator Translate( ITranslationContext context )
|
|
|
+ {
|
|
|
+ EnsureInitialized();
|
|
|
+
|
|
|
+ while( _initializing && !_failed )
|
|
|
+ {
|
|
|
+ yield return new WaitForSeconds( 0.2f );
|
|
|
+ }
|
|
|
+
|
|
|
+ if( _failed )
|
|
|
+ {
|
|
|
+ context.Fail( "Translator failed.", null );
|
|
|
+ yield break;
|
|
|
+ }
|
|
|
+
|
|
|
+ var result = new StreamReaderResult();
|
|
|
+ var id = Guid.NewGuid();
|
|
|
+
|
|
|
+ lock( _sync )
|
|
|
+ {
|
|
|
+ _pendingRequests[ id ] = result;
|
|
|
+ }
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var request = new TranslationRequest
|
|
|
+ {
|
|
|
+ Id = id,
|
|
|
+ SourceLanguage = context.SourceLanguage,
|
|
|
+ DestinationLanguage = context.DestinationLanguage,
|
|
|
+ UntranslatedText = context.UntranslatedText
|
|
|
+ };
|
|
|
+ var payload = ExtProtocolConvert.Encode( request );
|
|
|
+
|
|
|
+ _process.StandardInput.WriteLine( payload );
|
|
|
+ }
|
|
|
+ catch( Exception e )
|
|
|
+ {
|
|
|
+ result.SetCompleted( null, e.Message );
|
|
|
+ }
|
|
|
+
|
|
|
+ // yield-wait for completion
|
|
|
+ if( Features.SupportsCustomYieldInstruction )
|
|
|
+ {
|
|
|
+ yield return result;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ while( !result.IsCompleted )
|
|
|
{
|
|
|
- context.Fail( "Error occurred while retrieving translation.", e );
|
|
|
+ yield return new WaitForSeconds( 0.2f );
|
|
|
}
|
|
|
}
|
|
|
- finally
|
|
|
+
|
|
|
+ if( result.Succeeded )
|
|
|
+ {
|
|
|
+ context.Complete( result.Result );
|
|
|
+ }
|
|
|
+ else
|
|
|
{
|
|
|
- result = null;
|
|
|
+ context.Fail( "Error occurred while retrieving translation." + Environment.NewLine + result.Error, null );
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private static void HandleProtocolMessage( StreamReaderResult result, ProtocolMessage message )
|
|
|
+ private void HandleProtocolMessage( ProtocolMessage message )
|
|
|
{
|
|
|
switch( message )
|
|
|
{
|
|
|
case TranslationResponse translationResponse:
|
|
|
- HandleTranslationResponse( result, translationResponse );
|
|
|
+ HandleTranslationResponse( translationResponse );
|
|
|
break;
|
|
|
case TranslationError translationResponse:
|
|
|
- HandleTranslationError( result, translationResponse );
|
|
|
+ HandleTranslationError( translationResponse );
|
|
|
break;
|
|
|
default:
|
|
|
- result.SetCompleted( null, "Received invalid response." );
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private static void HandleTranslationResponse( StreamReaderResult result, TranslationResponse message )
|
|
|
+ private void HandleTranslationResponse( TranslationResponse message )
|
|
|
{
|
|
|
- result.SetCompleted( message.TranslatedText, null );
|
|
|
+ lock( _sync )
|
|
|
+ {
|
|
|
+ if( _pendingRequests.TryGetValue( message.Id, out var result ) )
|
|
|
+ {
|
|
|
+ result.SetCompleted( message.TranslatedText, null );
|
|
|
+ _pendingRequests.Remove( message.Id );
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- private static void HandleTranslationError( StreamReaderResult result, TranslationError message )
|
|
|
+ private void HandleTranslationError( TranslationError message )
|
|
|
{
|
|
|
- result.SetCompleted( null, message.Reason );
|
|
|
+ lock( _sync )
|
|
|
+ {
|
|
|
+ if( _pendingRequests.TryGetValue( message.Id, out var result ) )
|
|
|
+ {
|
|
|
+ result.SetCompleted( null, message.Reason );
|
|
|
+ _pendingRequests.Remove( message.Id );
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private class StreamReaderResult : CustomYieldInstructionShim
|
|
|
{
|
|
|
+ public StreamReaderResult()
|
|
|
+ {
|
|
|
+ StartTime = Time.realtimeSinceStartup;
|
|
|
+ }
|
|
|
+
|
|
|
public void SetCompleted( string result, string error )
|
|
|
{
|
|
|
IsCompleted = true;
|
|
@@ -154,6 +247,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.ExtProtocol
|
|
|
|
|
|
public override bool keepWaiting => !IsCompleted;
|
|
|
|
|
|
+ public float StartTime { get; set; }
|
|
|
+
|
|
|
public string Result { get; set; }
|
|
|
|
|
|
public string Error { get; set; }
|
|
@@ -171,10 +266,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.ExtProtocol
|
|
|
{
|
|
|
if( disposing )
|
|
|
{
|
|
|
- if(_process != null )
|
|
|
+ if( _process != null )
|
|
|
{
|
|
|
_process.Kill();
|
|
|
_process.Dispose();
|
|
|
+ _thread.Abort();
|
|
|
}
|
|
|
}
|
|
|
|