using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
namespace io.github.hatayama.uLoopMCP
{
///
/// Manages the caching of compilation results.
/// Avoids recompiling the same code by hashing requests with SHA256.
///
public class CompilationCacheManager
{
private const int MaxCacheEntries = 32;
private readonly Dictionary _compilationCache = new();
private readonly Queue _cacheOrder = new();
public CompilationResult CheckCache(CompilationRequest request)
{
string cacheKey = GenerateCacheKey(request);
if (_compilationCache.TryGetValue(cacheKey, out CachedCompilationResult cachedResult))
{
return CloneCompilationResult(cachedResult);
}
return null;
}
public void CacheResultIfSuccessful(CompilationResult result, CompilationRequest request)
{
if (result.Success && result.CompiledAssembly != null)
{
string cacheKey = GenerateCacheKey(request);
CachedCompilationResult cachedResult = CreateCachedCompilationResult(result);
if (_compilationCache.ContainsKey(cacheKey))
{
_compilationCache[cacheKey] = cachedResult;
return;
}
// Dynamic assemblies cannot be unloaded from the default AppDomain,
// so the cache keeps only a small hot set instead of retaining every snippet forever.
if (_compilationCache.Count >= MaxCacheEntries)
{
string oldestKey = _cacheOrder.Dequeue();
_compilationCache.Remove(oldestKey);
}
_compilationCache[cacheKey] = cachedResult;
_cacheOrder.Enqueue(cacheKey);
}
}
public void ClearCache()
{
_compilationCache.Clear();
_cacheOrder.Clear();
}
public string GenerateCacheKey(CompilationRequest request)
{
StringBuilder keyBuilder = new StringBuilder();
keyBuilder.Append(request.Code);
keyBuilder.Append("|");
keyBuilder.Append(request.ClassName ?? "");
keyBuilder.Append("|");
keyBuilder.Append(request.Namespace ?? "");
if (request.AdditionalReferences != null && request.AdditionalReferences.Any())
{
keyBuilder.Append("|");
keyBuilder.Append(string.Join(",", request.AdditionalReferences.OrderBy(r => r)));
}
using (SHA256 sha256 = SHA256.Create())
{
byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(keyBuilder.ToString()));
return Convert.ToBase64String(hashBytes);
}
}
private static CachedCompilationResult CreateCachedCompilationResult(CompilationResult result)
{
return new CachedCompilationResult(
result.CompiledAssembly,
CloneCompilationErrors(result.Errors),
CloneStrings(result.Warnings),
result.UpdatedCode,
result.HasSecurityViolations,
CloneSecurityViolations(result.SecurityViolations),
result.FailureReason,
CloneAmbiguousTypeCandidates(result.AmbiguousTypeCandidates),
CloneStrings(result.AutoInjectedNamespaces),
CloneStrings(result.Timings),
CloneStrings(result.AdvisoryLogs),
result.CompilationBackendKind);
}
private static CompilationResult CloneCompilationResult(CachedCompilationResult cachedResult)
{
return new CompilationResult
{
Success = true,
CompiledAssembly = cachedResult.CompiledAssembly,
Errors = CloneCompilationErrors(cachedResult.Errors),
Warnings = CloneStrings(cachedResult.Warnings),
UpdatedCode = cachedResult.UpdatedCode,
HasSecurityViolations = cachedResult.HasSecurityViolations,
SecurityViolations = CloneSecurityViolations(cachedResult.SecurityViolations),
FailureReason = cachedResult.FailureReason,
AmbiguousTypeCandidates = CloneAmbiguousTypeCandidates(cachedResult.AmbiguousTypeCandidates),
AutoInjectedNamespaces = CloneStrings(cachedResult.AutoInjectedNamespaces),
Timings = BuildCachedCompilationTimings(cachedResult.CompilationBackendKind),
AdvisoryLogs = CloneStrings(cachedResult.AdvisoryLogs),
CompilationBackendKind = cachedResult.CompilationBackendKind
};
}
private static List BuildCachedCompilationTimings(
DynamicCompilationBackendKind compilationBackendKind)
{
List timings = new List
{
"[Perf] ReferenceResolution: 0.0ms",
"[Perf] Build: 0.0ms",
"[Perf] AssemblyLoad: 0.0ms"
};
switch (compilationBackendKind)
{
case DynamicCompilationBackendKind.SharedRoslynWorker:
timings.Add("[Perf] Backend: SharedRoslynWorker");
break;
case DynamicCompilationBackendKind.OneShotRoslyn:
timings.Add("[Perf] Backend: OneShotRoslyn");
break;
case DynamicCompilationBackendKind.AssemblyBuilderFallback:
timings.Add("[Perf] Backend: AssemblyBuilderFallback");
break;
}
timings.Add("[Perf] CacheHit: true");
return timings;
}
private static List CloneCompilationErrors(List errors)
{
List clonedErrors = new List();
if (errors == null)
{
return clonedErrors;
}
foreach (CompilationError error in errors)
{
clonedErrors.Add(new CompilationError
{
Message = error.Message,
Line = error.Line,
Column = error.Column,
ErrorCode = error.ErrorCode
});
}
return clonedErrors;
}
private static List CloneSecurityViolations(List securityViolations)
{
List clonedViolations = new List();
if (securityViolations == null)
{
return clonedViolations;
}
foreach (SecurityViolation violation in securityViolations)
{
clonedViolations.Add(new SecurityViolation
{
Type = violation.Type,
Description = violation.Description,
LineNumber = violation.LineNumber,
CodeSnippet = violation.CodeSnippet,
Message = violation.Message,
ApiName = violation.ApiName,
Location = violation.Location
});
}
return clonedViolations;
}
private static Dictionary> CloneAmbiguousTypeCandidates(
Dictionary> ambiguousTypeCandidates)
{
Dictionary> clonedCandidates = new Dictionary>();
if (ambiguousTypeCandidates == null)
{
return clonedCandidates;
}
foreach (KeyValuePair> entry in ambiguousTypeCandidates)
{
clonedCandidates[entry.Key] = CloneStrings(entry.Value);
}
return clonedCandidates;
}
private static List CloneStrings(List values)
{
if (values == null)
{
return new List();
}
return new List(values);
}
private sealed class CachedCompilationResult
{
public Assembly CompiledAssembly { get; }
public List Errors { get; }
public List Warnings { get; }
public string UpdatedCode { get; }
public bool HasSecurityViolations { get; }
public List SecurityViolations { get; }
public CompilationFailureReason FailureReason { get; }
public Dictionary> AmbiguousTypeCandidates { get; }
public List AutoInjectedNamespaces { get; }
public List Timings { get; }
public List AdvisoryLogs { get; }
public DynamicCompilationBackendKind CompilationBackendKind { get; }
public CachedCompilationResult(
Assembly compiledAssembly,
List errors,
List warnings,
string updatedCode,
bool hasSecurityViolations,
List securityViolations,
CompilationFailureReason failureReason,
Dictionary> ambiguousTypeCandidates,
List autoInjectedNamespaces,
List timings,
List advisoryLogs,
DynamicCompilationBackendKind compilationBackendKind)
{
CompiledAssembly = compiledAssembly;
Errors = errors;
Warnings = warnings;
UpdatedCode = updatedCode;
HasSecurityViolations = hasSecurityViolations;
SecurityViolations = securityViolations;
FailureReason = failureReason;
AmbiguousTypeCandidates = ambiguousTypeCandidates;
AutoInjectedNamespaces = autoInjectedNamespaces;
Timings = timings;
AdvisoryLogs = advisoryLogs;
CompilationBackendKind = compilationBackendKind;
}
}
}
}