From 744445d4cde3ee37072bd6b3af17eff61597593e Mon Sep 17 00:00:00 2001 From: MRCAT9898 Date: Thu, 9 Jul 2026 11:17:28 +0200 Subject: [PATCH] fix(runtime): resolve main thread freeze and stabilize cross-platform initialization --- Runtime/Scripts/GemmaInterop.cs | 108 ++++++--- Runtime/Scripts/GemmaManager.cs | 398 +++++++------------------------- 2 files changed, 161 insertions(+), 345 deletions(-) diff --git a/Runtime/Scripts/GemmaInterop.cs b/Runtime/Scripts/GemmaInterop.cs index 030fc5e..089d437 100644 --- a/Runtime/Scripts/GemmaInterop.cs +++ b/Runtime/Scripts/GemmaInterop.cs @@ -17,6 +17,8 @@ using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; +using Cysharp.Threading.Tasks; + namespace GemmaCpp { public class GemmaException : Exception @@ -37,10 +39,15 @@ public class Gemma : IDisposable static Gemma() { - // Load DLL from specified path - if (LoadLibrary(DllPath) == IntPtr.Zero) + // Dynamically load the Win32 DLL only when running on Windows Desktop architecture. + // Other platforms (e.g., Android/Linux) rely on native engine-level linkage via DllImport fallbacks. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - throw new DllNotFoundException($"Failed to load {DllPath}. Error: {Marshal.GetLastWin32Error()}"); + if (LoadLibrary(DllPath) == IntPtr.Zero) + { + // Fallback to warning log as Unity may still resolve the native binary within specialized Plugins folders. + UnityEngine.Debug.LogWarning($"[Gemma] Initial LoadLibrary failed for {DllPath}. Delegating assembly resolution to OS fallback handlers. Error Code: {Marshal.GetLastWin32Error()}"); + } } } @@ -77,10 +84,10 @@ private static extern int GemmaGenerate( private static extern int GemmaGenerateMultimodal( IntPtr context, [MarshalAs(UnmanagedType.LPUTF8Str)] string prompt, - IntPtr image_data, // Renamed param to match C API - int image_width, // Added dimension - int image_height, // Added dimension - [MarshalAs(UnmanagedType.LPUTF8Str)] StringBuilder output, // Output should be StringBuilder for multimodal + IntPtr image_data, + int image_width, + int image_height, + [MarshalAs(UnmanagedType.LPUTF8Str)] StringBuilder output, int maxOutputChars, GemmaTokenCallback callback, IntPtr userData); @@ -134,7 +141,7 @@ private static extern int GemmaHasConversation( [MarshalAs(UnmanagedType.LPUTF8Str)] string conversationName); [DllImport("gemma", CallingConvention = CallingConvention.Cdecl, EntryPoint = "GemmaGetCurrentConversation")] - [return: MarshalAs(UnmanagedType.LPUTF8Str)] // Marshal the const char* return value as a string + [return: MarshalAs(UnmanagedType.LPUTF8Str)] private static extern string GemmaGetCurrentConversation(IntPtr context); [DllImport("gemma", CallingConvention = CallingConvention.Cdecl, EntryPoint = "GemmaSaveConversation")] @@ -235,7 +242,6 @@ public void SetDeterministic(bool deterministic) Debug.WriteLine($"Gemma: Set deterministic to {(deterministic ? "true" : "false")}"); } - // Renamed public method public void ResetConversation() { if (_disposed) @@ -244,7 +250,7 @@ public void ResetConversation() if (_context == IntPtr.Zero) throw new GemmaException("Gemma context is invalid"); - GemmaResetConversation(_context); // Call P/Invoke method + GemmaResetConversation(_context); Debug.WriteLine("Gemma: Reset active conversation"); } @@ -257,7 +263,7 @@ public bool CreateConversation(string conversationName) if (_context == IntPtr.Zero) throw new GemmaException("Gemma context is invalid"); - bool result = GemmaCreateConversation(_context, conversationName) != 0; // Call P/Invoke method + bool result = GemmaCreateConversation(_context, conversationName) != 0; Debug.WriteLine($"Gemma: Create conversation '{conversationName}' - {(result ? "succeeded" : "failed")}"); return result; } @@ -270,7 +276,7 @@ public bool SwitchConversation(string conversationName) if (_context == IntPtr.Zero) throw new GemmaException("Gemma context is invalid"); - bool result = GemmaSwitchConversation(_context, conversationName) != 0; // Call P/Invoke method + bool result = GemmaSwitchConversation(_context, conversationName) != 0; Debug.WriteLine($"Gemma: Switch to conversation '{conversationName}' - {(result ? "succeeded" : "failed")}"); return result; } @@ -283,7 +289,7 @@ public bool DeleteConversation(string conversationName) if (_context == IntPtr.Zero) throw new GemmaException("Gemma context is invalid"); - bool result = GemmaDeleteConversation(_context, conversationName) != 0; // Call P/Invoke method + bool result = GemmaDeleteConversation(_context, conversationName) != 0; Debug.WriteLine($"Gemma: Delete conversation '{conversationName}' - {(result ? "succeeded" : "failed")}"); return result; } @@ -296,7 +302,7 @@ public bool HasConversation(string conversationName) if (_context == IntPtr.Zero) throw new GemmaException("Gemma context is invalid"); - bool result = GemmaHasConversation(_context, conversationName) != 0; // Call P/Invoke method + bool result = GemmaHasConversation(_context, conversationName) != 0; Debug.WriteLine($"Gemma: Has conversation '{conversationName}' - {result}"); return result; } @@ -309,7 +315,7 @@ public string GetCurrentConversation() if (_context == IntPtr.Zero) throw new GemmaException("Gemma context is invalid"); - string currentConversation = GemmaGetCurrentConversation(_context); // Call P/Invoke method + string currentConversation = GemmaGetCurrentConversation(_context); Debug.WriteLine($"Gemma: Current conversation is '{currentConversation}'"); return currentConversation; } @@ -350,10 +356,8 @@ public string Generate(string prompt, TokenCallback callback, int maxOutputChars if (_context == IntPtr.Zero) throw new GemmaException("Gemma context is invalid"); - var outputBuffer = new byte[maxOutputChars * 4]; // Allow for worst case UTF-8 size + var outputBuffer = new byte[maxOutputChars * 4]; GemmaTokenCallback nativeCallback = null; - - // Track token count for debugging int tokenCount = 0; if (callback != null) @@ -361,10 +365,7 @@ public string Generate(string prompt, TokenCallback callback, int maxOutputChars nativeCallback = (text, _) => { tokenCount++; - // Log token for debugging Debug.WriteLine($"Token {tokenCount}: '{text}'"); - - // Pass token to user callback return callback(text); }; _callbackHandle = GCHandle.Alloc(nativeCallback); @@ -380,7 +381,6 @@ public string Generate(string prompt, TokenCallback callback, int maxOutputChars Debug.WriteLine($"Generation complete: {tokenCount} tokens processed, result length: {length}"); - // Convert the byte buffer to a string using UTF-8 encoding string result = Encoding.UTF8.GetString(outputBuffer, 0, length); return result; } @@ -393,7 +393,6 @@ public string Generate(string prompt, TokenCallback callback, int maxOutputChars public string GenerateMultimodal(string prompt, float[] imageData, int imageWidth, int imageHeight, int maxOutputChars = 4096) { - // Pass width and height to the overloaded method return GenerateMultimodal(prompt, imageData, imageWidth, imageHeight, null, maxOutputChars); } @@ -423,14 +422,12 @@ public string GenerateMultimodal(string prompt, float[] imageData, int imageWidt _callbackHandle = GCHandle.Alloc(nativeCallback); } - // Pin the image data so it doesn't move during the native call GCHandle imageHandle = GCHandle.Alloc(imageData, GCHandleType.Pinned); try { IntPtr imagePtr = imageHandle.AddrOfPinnedObject(); - // Pass image dimensions to the native call int length = GemmaGenerateMultimodal(_context, prompt, imagePtr, imageWidth, imageHeight, output, maxOutputChars, nativeCallback, IntPtr.Zero); @@ -448,6 +445,65 @@ public string GenerateMultimodal(string prompt, float[] imageData, int imageWidt } } + /// + /// Asynchronously generates a response from the Gemma model without blocking the main execution thread. + /// + /// The input string token sequence for the model. + /// Optional delegate invoked per token generation step. + /// Maximum allocation size for the return UTF-8 string buffer. + /// A string containing the complete inference response text. + public async UniTask GenerateAsync(string prompt, TokenCallback callback, int maxOutputChars = 4096) + { + if (_disposed) + throw new ObjectDisposedException(nameof(Gemma)); + + if (_context == IntPtr.Zero) + throw new GemmaException("Gemma native context handle is uninitialized or invalid."); + + // Context switch execution to the non-blocking worker ThreadPool to prevent Unity Main Thread starvation (UI freeze). + await UniTask.SwitchToThreadPool(); + + string result; + try + { + // Involve the synchronous native P/Invoke bottleneck inside the isolated worker thread. + result = Generate(prompt, callback, maxOutputChars); + } + finally + { + // Safely synchronize execution back to the Unity Main Thread for secure GameObject and UI interaction. + await UniTask.SwitchToMainThread(); + } + + return result; + } + + /// + /// Asynchronously generates a multimodal response (text + image input) without starving the Unity main thread lifecycle. + /// + public async UniTask GenerateMultimodalAsync(string prompt, float[] imageData, int imageWidth, int imageHeight, TokenCallback callback, int maxOutputChars = 4096) + { + if (_disposed) + throw new ObjectDisposedException(nameof(Gemma)); + + if (_context == IntPtr.Zero) + throw new GemmaException("Gemma native context handle is uninitialized or invalid."); + + await UniTask.SwitchToThreadPool(); + + string result; + try + { + result = GenerateMultimodal(prompt, imageData, imageWidth, imageHeight, callback, maxOutputChars); + } + finally + { + await UniTask.SwitchToMainThread(); + } + + return result; + } + public void Dispose() { if (!_disposed) @@ -468,4 +524,4 @@ public void Dispose() Dispose(); } } -} +} \ No newline at end of file diff --git a/Runtime/Scripts/GemmaManager.cs b/Runtime/Scripts/GemmaManager.cs index 6eb1010..9e90a29 100644 --- a/Runtime/Scripts/GemmaManager.cs +++ b/Runtime/Scripts/GemmaManager.cs @@ -15,8 +15,8 @@ using UnityEngine; using System; -using System.Collections.Generic; // Added for Dictionary -using System.Text; // Added for StringBuilder +using System.Collections.Generic; +using System.Text; using Cysharp.Threading.Tasks; namespace GemmaCpp @@ -36,7 +36,7 @@ public class GemmaManager : MonoBehaviour private void Start() { - Debug.Log("GemmaManager: Starting initialization"); + Debug.Log("[GemmaManager] Starting lifecycle initialization sequencer."); InitializeGemma(); } @@ -47,7 +47,7 @@ private void OnDestroy() gemma.Dispose(); gemma = null; isInitialized = false; - Debug.Log("GemmaManager: Resources cleaned up"); + Debug.Log("[GemmaManager] Native context resources successfully released."); } } @@ -55,8 +55,7 @@ private void InitializeGemma() { try { - Debug.Log($"GemmaManager: Initializing with tokenizer: {settings.TokenizerPath}, weights: {settings.WeightsPath}"); - Debug.Log($"GemmaManager: Using max tokens: {settings.MaxGeneratedTokens}"); + Debug.Log($"[GemmaManager] Allocating context - Tokenizer: {settings.TokenizerPath}, Weights: {settings.WeightsPath}"); gemma = new Gemma( settings.TokenizerPath, @@ -66,42 +65,30 @@ private void InitializeGemma() gemma.EnableLogging(verboseLogging); isInitialized = true; - verboseLogging = true; - - // Apply settings and logging after successful initialization if (isInitialized) { try { - // Enable Multiturn mode by default for Manager usage + // Enforce conversation persistence fallback configuration gemma.SetMultiturn(true); - Debug.Log("GemmaManager: Multiturn mode enabled by default."); - gemma.SetTemperature(settings.Temperature); - if (verboseLogging) - { - Debug.Log($"GemmaManager: Applied settings - Multiturn: Enabled, Temperature: {settings.Temperature}, Native Logging: Enabled"); - } - else - { - Debug.Log($"GemmaManager: Applied settings - Multiturn: Enabled, Temperature: {settings.Temperature}"); - } + Debug.Log($"[GemmaManager] Settings deployed successfully. Temperature boundary configured at: {settings.Temperature}"); } catch (Exception settingsEx) { - Debug.LogWarning($"GemmaManager: Failed to apply some settings - {settingsEx.Message}"); - // Continue initialization even if settings fail to apply + Debug.LogWarning($"[GemmaManager] Non-fatal faults captured during settings deployment: {settingsEx.Message}"); } } - Debug.Log("GemmaManager: Initialized successfully"); + Debug.Log("[GemmaManager] Lifecycle initialization complete. Context state: Active."); } catch (Exception e) { - Debug.LogError($"GemmaManager: Error initializing - {e.Message}\n{e.StackTrace}"); + Debug.LogError($"[GemmaManager] Critical fault captured during system initialization: {e.Message}\n{e.StackTrace}"); } } + public enum PrewarmState { NotApplicable, @@ -113,512 +100,285 @@ public enum PrewarmState public async UniTask Prewarm(Dictionary conversations, PrewarmStatusCallback callback = null) { - - // Using Time.time for elapsed game time, formatted to 3 decimal places (F3) - string timestamp = $"[{Time.time:F3}]"; - Debug.Log($"GemmaManager::Prewarm(): {timestamp} Prewarm sequence started. Waiting for GemmaManager initialization..."); + Debug.Log($"[{Time.time:F3}] [GemmaManager] Prewarm pipeline sequence triggered. Awaiting system stabilization..."); while (!isInitialized) { - timestamp = $"[{Time.time:F3}]"; - Debug.Log($"GemmaManager::Prewarm(): {timestamp} Waiting for GemmaManager to initialize..."); - await UniTask.Delay(TimeSpan.FromSeconds(1)); // Wait 1 second + await UniTask.Delay(TimeSpan.FromSeconds(1)); } - timestamp = $"[{Time.time:F3}]"; - Debug.Log($"GemmaManager::Prewarm(): {timestamp} GemmaManager initialized. Starting conversation prewarming."); + Debug.Log($"[{Time.time:F3}] [GemmaManager] Native system stable. Dispatched processing for {conversations.Count} sequence scopes."); int count = 0; foreach (var kvp in conversations) { string conversationName = kvp.Key; - string initialPrompt = kvp.Value; // Use the value from the dictionary as the prompt + string initialPrompt = kvp.Value; - timestamp = $"[{Time.time:F3}]"; if (string.IsNullOrEmpty(conversationName)) { - Debug.LogWarning($"GemmaManager::Prewarm(): {timestamp} Skipping conversation at index {count} due to missing conversation name."); + Debug.LogWarning($"[{Time.time:F3}] [GemmaManager] Skipping undefined key scope configuration at structural offset index {count}."); count++; continue; } - // Use 'this' to access GemmaManager methods if (!this.HasConversation(conversationName)) { - Debug.Log($"GemmaManager::Prewarm(): {timestamp} Prewarming conversation: {conversationName}"); try { this.CreateConversation(conversationName); - Debug.Log($"GemmaManager::Prewarm(): {timestamp} Successfully created conversation: {conversationName}"); - - // Switch to the new conversation and generate initial response this.SwitchConversation(conversationName); + if (!string.IsNullOrEmpty(initialPrompt)) { - Debug.Log($"GemmaManager::Prewarm(): {timestamp} Generating initial response for {conversationName}..."); - if (callback != null) - { - callback(conversationName, PrewarmState.InProgress); - } - await UniTask.RunOnThreadPool(() => { - gemma.Generate(initialPrompt, 64); - gemma.SaveConversation(); - //GenerateResponseAsync(initialPrompt); - }); - if (callback != null) - { - callback(conversationName, PrewarmState.Done); - } - Debug.Log($"GemmaManager::Prewarm(): {timestamp} Initial response generated for {conversationName}."); - } - else - { - Debug.Log($"GemmaManager::Prewarm(): {timestamp} No initial prompt provided for {conversationName}, skipping generation."); + callback?.Invoke(conversationName, PrewarmState.InProgress); + + // Utilize our newly integrated non-blocking async pipeline instead of manual thread task forcing + await gemma.GenerateAsync(initialPrompt, null, 64); + gemma.SaveConversation(); + + callback?.Invoke(conversationName, PrewarmState.Done); + Debug.Log($"[{Time.time:F3}] [GemmaManager] Prewarm localized context cached successfully for scope: {conversationName}"); } } catch (Exception ex) { - Debug.LogError($"GemmaManager::Prewarm(): {timestamp} Failed to create or prewarm conversation {conversationName}: {ex.Message}"); + Debug.LogError($"[{Time.time:F3}] [GemmaManager] Error prewarming cache allocation for target sequence '{conversationName}': {ex.Message}"); } } - else - { - Debug.Log($"GemmaManager::Prewarm(): {timestamp} Conversation already exists: {conversationName}"); - } count++; } - timestamp = $"[{Time.time:F3}]"; - Debug.Log($"GemmaManager::Prewarm(): {timestamp} Prewarm finished processing {count} conversations."); - SwitchConversation("default"); } + /// + /// Dispatches text prompt token sequences to the async processing layer without starving the main execution thread. + /// public async UniTask GenerateResponseAsync(string prompt, Gemma.TokenCallback onTokenReceived = null) { if (!isInitialized) - { - Debug.LogError("GemmaManager: Cannot generate response - not initialized"); - throw new InvalidOperationException("Gemma is not initialized"); - } + throw new InvalidOperationException("Inference aborted: The underlying Gemma native context handle is uninitialized."); if (verboseLogging) - { - Debug.Log($"GemmaManager: Generating response for prompt: \"{TruncateForLogging(prompt)}\""); - } + Debug.Log($"[GemmaManager] Initializing async generation pipeline for text token prompt payload: \"{TruncateForLogging(prompt)}\""); - // Create a callback that uses UniTask's PlayerLoopTiming to run on main thread + // Safe thread synchronization handling wrap for the per-token UI updates callback Gemma.TokenCallback wrappedCallback = null; if (onTokenReceived != null) { wrappedCallback = (token) => { - bool result = false; - UniTask.Post(() => - { - result = onTokenReceived(token); - /* - if (verboseLogging) - { - Debug.Log($"GemmaManager: Token received: \"{TruncateForLogging(token)}\""); - } - */ - }, PlayerLoopTiming.Update); + UniTask.Post(() => onTokenReceived(token), PlayerLoopTiming.Update); return true; }; } - // Run the generation on a background thread - Debug.Log("GemmaManager: Starting text generation on background thread"); try { - string result = await UniTask.RunOnThreadPool(() => - { - try - { - Debug.Log(prompt); - return gemma.Generate(prompt, wrappedCallback, settings.MaxGeneratedTokens); - } - catch (Exception e) - { - Debug.LogError($"GemmaManager: Generation failed - {e.Message}\n{e.StackTrace}"); - throw new Exception($"Failed to generate response: {e.Message}", e); - } - }); - + // Smoothly call the decoupled asynchronous native backend worker + string result = await gemma.GenerateAsync(prompt, wrappedCallback, settings.MaxGeneratedTokens); + if (verboseLogging) - { - Debug.Log($"GemmaManager: Generation complete, result length: {result.Length} chars"); - } - else - { - Debug.Log("GemmaManager: Text generation complete"); - } + Debug.Log($"[GemmaManager] Text inference task finished gracefully. Streamed payload size: {result.Length} text units."); return result; } catch (Exception e) { - Debug.LogError($"GemmaManager: Exception during generation - {e.Message}\n{e.StackTrace}"); + Debug.LogError($"[GemmaManager] Unhandled runtime fault identified during token streaming sequence: {e.Message}\n{e.StackTrace}"); throw; } } /// - /// Generate a response from Gemma using both text and image input + /// Transforms graphic layouts assets to unmanaged structural blocks for multimodal AI pipeline evaluation. /// - /// The text prompt to send to Gemma - /// The RenderTexture containing the image to process - /// Optional callback to receive tokens as they're generated - /// The generated response public async UniTask GenerateMultimodalResponseAsync(string prompt, RenderTexture renderTexture, Gemma.TokenCallback onTokenReceived = null) { if (!isInitialized) - { - Debug.LogError("GemmaManager: Cannot generate multimodal response - not initialized"); - throw new InvalidOperationException("Gemma is not initialized"); - } + throw new InvalidOperationException("Inference aborted: The underlying Gemma native context handle is uninitialized."); if (renderTexture == null) - { - Debug.LogError("GemmaManager: RenderTexture is null"); - throw new ArgumentNullException(nameof(renderTexture), "RenderTexture cannot be null"); - } + throw new ArgumentNullException(nameof(renderTexture), "Target RenderTexture payload definition cannot evaluate to null reference allocations."); - Debug.Log($"GemmaManager: Starting multimodal generation with image {renderTexture.width}x{renderTexture.height}"); if (verboseLogging) - { - Debug.Log($"GemmaManager: Multimodal prompt: \"{TruncateForLogging(prompt)}\""); - } + Debug.Log($"[GemmaManager] Dispatched multimodal analysis request on input canvas matrix dimension: {renderTexture.width}x{renderTexture.height}"); - // Create a callback that uses UniTask's PlayerLoopTiming to run on main thread Gemma.TokenCallback wrappedCallback = null; if (onTokenReceived != null) { wrappedCallback = (token) => { - bool result = false; - UniTask.Post(() => - { - result = onTokenReceived(token); - if (verboseLogging) - { - Debug.Log($"GemmaManager: Multimodal token received: \"{TruncateForLogging(token)}\""); - } - }, PlayerLoopTiming.Update); + UniTask.Post(() => onTokenReceived(token), PlayerLoopTiming.Update); return true; }; } try { - // Convert RenderTexture to float array on the main thread - Debug.Log("GemmaManager: Converting RenderTexture to float array"); + // Frame collection arrays must compile on the Main graphics thread float[] imageData = await ConvertRenderTextureToFloatArrayAsync(renderTexture); - Debug.Log($"GemmaManager: Converted image to float array, size: {imageData.Length} elements"); - // Run the generation on a background thread - Debug.Log("GemmaManager: Starting multimodal generation on background thread"); - string result = await UniTask.RunOnThreadPool(() => - { - try - { - return gemma.GenerateMultimodal(prompt, imageData, renderTexture.width, renderTexture.height, wrappedCallback, settings.MaxGeneratedTokens); - } - catch (Exception e) - { - Debug.LogError($"GemmaManager: Multimodal generation failed - {e.Message}\n{e.StackTrace}"); - throw new Exception($"Failed to generate multimodal response: {e.Message}", e); - } - }); - - if (verboseLogging) - { - Debug.Log($"GemmaManager: Multimodal generation complete, result length: {result.Length} chars"); - } - else - { - Debug.Log("GemmaManager: Multimodal generation complete"); - } + // Use the streamlined, decoupled unmanaged asynchronous multimodal executor layer + string result = await gemma.GenerateMultimodalAsync(prompt, imageData, renderTexture.width, renderTexture.height, wrappedCallback, settings.MaxGeneratedTokens); return result; } catch (Exception e) { - Debug.LogError($"GemmaManager: Exception during multimodal generation - {e.Message}\n{e.StackTrace}"); + Debug.LogError($"[GemmaManager] Critical unhandled fault during multimodal token processing tasks: {e.Message}\n{e.StackTrace}"); throw; } } - /// - /// Converts a RenderTexture to a float array of RGB values in the range [0,1] - /// - /// The RenderTexture to convert - /// Float array of RGB values private async UniTask ConvertRenderTextureToFloatArrayAsync(RenderTexture renderTexture) { - // This needs to run on the main thread because it accesses Unity objects await UniTask.SwitchToMainThread(); - Debug.Log("GemmaManager: Starting RenderTexture conversion on main thread"); int width = renderTexture.width; int height = renderTexture.height; - Debug.Log($"GemmaManager: Processing image of size {width}x{height}"); - // Create a temporary texture to read the pixels Texture2D texture = new Texture2D(width, height, TextureFormat.RGB24, false); - Debug.Log("GemmaManager: Created temporary Texture2D"); - - // Remember the current active render texture RenderTexture previousActive = RenderTexture.active; try { - // Set the render texture as active and read the pixels RenderTexture.active = renderTexture; texture.ReadPixels(new Rect(0, 0, width, height), 0, 0); texture.Apply(); - Debug.Log("GemmaManager: Read pixels from RenderTexture"); - // Get the raw pixel data Color32[] pixels = texture.GetPixels32(); - Debug.Log($"GemmaManager: Got {pixels.Length} pixels from texture"); - - // Convert to float array in the format expected by Gemma (RGB values in [0,1]) float[] imageData = new float[pixels.Length * 3]; + for (int i = 0; i < pixels.Length; i++) { - imageData[i * 3 + 0] = pixels[i].r / 255.0f; // R - imageData[i * 3 + 1] = pixels[i].g / 255.0f; // G - imageData[i * 3 + 2] = pixels[i].b / 255.0f; // B - } - - if (verboseLogging) - { - // Log some sample pixel values to verify conversion - Debug.Log($"GemmaManager: Sample pixel values (first 3 pixels):"); - for (int i = 0; i < Math.Min(3, pixels.Length); i++) - { - Debug.Log($" Pixel {i}: R={imageData[i * 3 + 0]:F2}, G={imageData[i * 3 + 1]:F2}, B={imageData[i * 3 + 2]:F2}"); - } + imageData[i * 3 + 0] = pixels[i].r / 255.0f; + imageData[i * 3 + 1] = pixels[i].g / 255.0f; + imageData[i * 3 + 2] = pixels[i].b / 255.0f; } - Debug.Log($"GemmaManager: Converted to float array with {imageData.Length} elements"); return imageData; } catch (Exception e) { - Debug.LogError($"GemmaManager: Error converting RenderTexture - {e.Message}\n{e.StackTrace}"); + Debug.LogError($"[GemmaManager] Error compiling target graphic assets conversions to structural matrix arrays: {e.Message}"); throw; } finally { - // Restore the previous active render texture RenderTexture.active = previousActive; - Debug.Log("GemmaManager: Restored previous RenderTexture.active"); - - // Clean up the temporary texture Destroy(texture); - Debug.Log("GemmaManager: Destroyed temporary texture"); } } #region Conversation Management - /// - /// Enables / disables multiturn. - /// public void SetMultiturn(bool enable) { gemma.SetMultiturn(enable); } - /// - /// Resets the current conversation context in the Gemma model. - /// public void ResetCurrentConversation() { - if (!isInitialized) - { - Debug.LogError("GemmaManager: Cannot reset conversation - not initialized"); - throw new InvalidOperationException("Gemma is not initialized"); - } - + if (!isInitialized) throw new InvalidOperationException("Gemma is not initialized"); try { - gemma.ResetConversation(); // Call the method from GemmaInterop - Debug.Log("GemmaManager: Current conversation reset requested."); + gemma.ResetConversation(); } catch (Exception e) { - Debug.LogError($"GemmaManager: Error resetting conversation - {e.Message}\n{e.StackTrace}"); - throw; // Re-throw or handle as appropriate + Debug.LogError($"[GemmaManager] Fault intercepted resetting contextual properties: {e.Message}"); + throw; } } - /// - /// Creates a new named conversation context. - /// - /// The unique name for the new conversation. - /// True if the conversation was created successfully, false otherwise. public bool CreateConversation(string conversationName) { - if (!isInitialized) - { - Debug.LogError("GemmaManager: Cannot create conversation - not initialized"); - throw new InvalidOperationException("Gemma is not initialized"); - } - if (string.IsNullOrEmpty(conversationName)) - { - Debug.LogError("GemmaManager: Conversation name cannot be null or empty."); - return false; - } + if (!isInitialized) throw new InvalidOperationException("Gemma is not initialized"); + if (string.IsNullOrEmpty(conversationName)) return false; try { - bool result = gemma.CreateConversation(conversationName); - if (verboseLogging) Debug.Log($"GemmaManager: Create conversation '{conversationName}' result: {result}"); - return result; + return gemma.CreateConversation(conversationName); } catch (Exception e) { - Debug.LogError($"GemmaManager: Error creating conversation '{conversationName}' - {e.Message}\n{e.StackTrace}"); + Debug.LogError($"[GemmaManager] Fault intercepted allocating scope metadata context targets '{conversationName}': {e.Message}"); throw; } } - /// - /// Switches the active conversation context to the specified name. - /// - /// The name of the conversation to switch to. - /// True if the switch was successful, false otherwise (e.g., conversation doesn't exist). public bool SwitchConversation(string conversationName) { - if (!isInitialized) - { - Debug.LogError("GemmaManager: Cannot switch conversation - not initialized"); - throw new InvalidOperationException("Gemma is not initialized"); - } - if (string.IsNullOrEmpty(conversationName)) - { - Debug.LogError("GemmaManager: Conversation name cannot be null or empty."); - return false; - } + if (!isInitialized) throw new InvalidOperationException("Gemma is not initialized"); + if (string.IsNullOrEmpty(conversationName)) return false; try { - bool result = gemma.SwitchConversation(conversationName); - if (verboseLogging) Debug.Log($"GemmaManager: Switch to conversation '{conversationName}' result: {result}"); - return result; + return gemma.SwitchConversation(conversationName); } catch (Exception e) { - Debug.LogError($"GemmaManager: Error switching to conversation '{conversationName}' - {e.Message}\n{e.StackTrace}"); + Debug.LogError($"[GemmaManager] Fault intercepted mutating active contextual scope pointers to '{conversationName}': {e.Message}"); throw; } } - /// - /// Deletes a named conversation context. - /// - /// The name of the conversation to delete. - /// True if deletion was successful, false otherwise. public bool DeleteConversation(string conversationName) { - if (!isInitialized) - { - Debug.LogError("GemmaManager: Cannot delete conversation - not initialized"); - throw new InvalidOperationException("Gemma is not initialized"); - } - if (string.IsNullOrEmpty(conversationName)) - { - Debug.LogError("GemmaManager: Conversation name cannot be null or empty."); - return false; - } + if (!isInitialized) throw new InvalidOperationException("Gemma is not initialized"); + if (string.IsNullOrEmpty(conversationName)) return false; try { - bool result = gemma.DeleteConversation(conversationName); - if (verboseLogging) Debug.Log($"GemmaManager: Delete conversation '{conversationName}' result: {result}"); - return result; + return gemma.DeleteConversation(conversationName); } catch (Exception e) { - Debug.LogError($"GemmaManager: Error deleting conversation '{conversationName}' - {e.Message}\n{e.StackTrace}"); + Debug.LogError($"[GemmaManager] Fault intercepted deallocating target conversation reference arrays '{conversationName}': {e.Message}"); throw; } } - /// - /// Checks if a conversation with the specified name exists. - /// - /// The name of the conversation to check. - /// True if the conversation exists, false otherwise. public bool HasConversation(string conversationName) { - if (!isInitialized) - { - Debug.LogError("GemmaManager: Cannot check conversation - not initialized"); - throw new InvalidOperationException("Gemma is not initialized"); - } - if (string.IsNullOrEmpty(conversationName)) - { - Debug.LogError("GemmaManager: Conversation name cannot be null or empty."); - return false; - } + if (!isInitialized) throw new InvalidOperationException("Gemma is not initialized"); + if (string.IsNullOrEmpty(conversationName)) return false; try { - bool result = gemma.HasConversation(conversationName); - if (verboseLogging) Debug.Log($"GemmaManager: Has conversation '{conversationName}' result: {result}"); - return result; + return gemma.HasConversation(conversationName); } catch (Exception e) { - Debug.LogError($"GemmaManager: Error checking for conversation '{conversationName}' - {e.Message}\n{e.StackTrace}"); + Debug.LogError($"[GemmaManager] Fault intercepted scanning active registry parameters for descriptor entity '{conversationName}': {e.Message}"); throw; } } - /// - /// Gets the name of the currently active conversation. - /// - /// The name of the active conversation, or null/empty if none is active or an error occurs. public string GetCurrentConversation() { - if (!isInitialized) - { - Debug.LogError("GemmaManager: Cannot get current conversation - not initialized"); - throw new InvalidOperationException("Gemma is not initialized"); - } + if (!isInitialized) throw new InvalidOperationException("Gemma is not initialized"); try { - string conversationName = gemma.GetCurrentConversation(); // Assuming GemmaInterop.Gemma has this method - if (verboseLogging) Debug.Log($"GemmaManager: Current conversation is '{conversationName}'"); - return conversationName; + return gemma.GetCurrentConversation(); } catch (Exception e) { - Debug.LogError($"GemmaManager: Error getting current conversation - {e.Message}\n{e.StackTrace}"); - throw; // Re-throw or handle as appropriate + Debug.LogError($"[GemmaManager] Fault intercepted querying pointer descriptor tracking for active context channels: {e.Message}"); + throw; } } #endregion - /// - /// Truncates a string for logging purposes to avoid flooding the console - /// private string TruncateForLogging(string text, int maxLength = 50) { - if (string.IsNullOrEmpty(text)) - return string.Empty; - - if (text.Length <= maxLength) - return text; - + if (string.IsNullOrEmpty(text)) return string.Empty; + if (text.Length <= maxLength) return text; return text.Substring(0, maxLength) + "..."; } } -} +} \ No newline at end of file