#if TOOLS using System; using System.Collections.Generic; using System.Reflection; using System.Threading; using Robust.Shared.Log; using Robust.Shared.Timing; using Robust.Shared.Utility; using Robust.Xaml; namespace Robust.Client.UserInterface.XAML.Proxy; /// /// This is a utility class that tracks the relationship between resource file names, /// Xamlx-compatible s, s that are interested in a /// given file, and implementations of Populate. /// internal sealed class XamlImplementationStorage { /// /// For each filename, we store its last known . /// /// /// When we compile the new implementation, we will use the same . /// private readonly Dictionary _fileUri = new(); /// /// For each filename, we store its last known content. /// /// /// This is known even for AOT-compiled code -- therefore, we can use this table /// to convert an AOT-compiled Control to a JIT-compiled one. /// private readonly Dictionary _fileContent = new(); /// /// For each filename, we store the type interested in this file. /// private readonly Dictionary _fileType = new(); private readonly Dictionary _fileTypeReverse = new(); /// /// For each type, store the JIT-compiled implementation of Populate. /// /// /// If no such implementation exists, then methods that would normally /// find and call a JIT'ed implementation will do nothing and return /// false instead. As an ultimate result, the AOT'ed implementation /// will be used. /// private readonly Dictionary _populateImplementations = new(); private readonly ISawmill _sawmill; private readonly XamlJitDelegate _jitDelegate; private readonly Lock _compileLock = new(); /// /// Create the storage. /// /// /// It would be weird to call this from any type outside of /// . /// /// the (shared) logger /// /// a delegate that calls the /// , possibly handling errors /// public XamlImplementationStorage(ISawmill sawmill, XamlJitDelegate jitDelegate) { _sawmill = sawmill; _jitDelegate = jitDelegate; } /// /// Inspect for types that declare a . /// /// /// We can only do hot reloading if we know this basic information. /// /// Note that even release-mode content artifacts contain this attribute. /// /// the assembly /// an IEnumerable of types with xaml metadata private IEnumerable<(Type, XamlMetadataAttribute)> TypesWithXamlMetadata(Assembly assembly) { foreach (var type in assembly.GetTypes()) { if (type.GetCustomAttribute() is not { } attr) { continue; } yield return (type, attr); } } /// /// Add all Xaml-annotated types from to this storage. /// /// /// We don't JIT these types, but we store enough info that we could JIT /// them if we wanted to. /// /// an assembly public void Add(Assembly assembly) { using var _ = _compileLock.EnterScope(); foreach (var (type, metadata) in TypesWithXamlMetadata(assembly)) { // this can fail, but if it does, that means something is _really_ wrong // with the compiler, or someone tried to write their own Xaml metadata Uri uri; try { uri = new Uri(metadata.Uri); } catch (UriFormatException) { throw new InvalidProgramException( $"XamlImplementationStorage encountered an malformed Uri in the metadata for {type.FullName}: " + $"{metadata.Uri}. this is a bug in XamlAotCompiler" ); } var fileName = metadata.FileName; var content = metadata.Content; _fileUri[fileName] = uri; _fileContent[fileName] = content; if (!_fileType.TryAdd(fileName, type)) { throw new InvalidProgramException( $"XamlImplementationStorage observed that two types were interested in the same Xaml filename: " + $"{fileName}. ({type.FullName} and {_fileType[fileName].FullName}). this is a bug in XamlAotCompiler" ); } _fileTypeReverse.Add(type, fileName); } } /// /// Quietly JIT every type with XAML metadata. /// /// /// This should have no visible effect except that the /// may dump some info messages into the terminal about cases where the /// hot reload failed. /// public void ForceReloadAll() { using var _ = _compileLock.EnterScope(); foreach (var (fileName, fileContent) in _fileContent) { SetImplementation(fileName, fileContent, true); } } /// /// Return true if calling on would not be a no-op. /// /// /// That is: if some type cares about the contents of . /// /// the filename /// true if not a no-op public bool CanSetImplementation(string fileName) { using var _ = _compileLock.EnterScope(); return _fileType.ContainsKey(fileName); } public MethodInfo? CompileType(Type type) { if (_fileTypeReverse.TryGetValue(type, out var fileName)) return SetImplementation(fileName, _fileContent[fileName], quiet: true); _sawmill.Warning($"Type {type} has no XAML file!"); return null; } /// /// Replace the implementation of by JIT-ing /// . /// /// /// If nothing cares about the implementation of , then this will do nothing. /// /// the name of the file whose implementation should be replaced /// the new implementation /// if true, then don't bother to log public MethodInfo? SetImplementation(string fileName, string fileContent, bool quiet) { using var _ = _compileLock.EnterScope(); if (!_fileType.TryGetValue(fileName, out var type)) { _sawmill.Warning($"SetImplementation called with {fileName}, but no types care about its contents"); return null; } var uri = _fileUri.GetValueOrDefault(fileName) ?? throw new InvalidProgramException("file URI missing (this is a bug in ImplementationStorage)"); if (!quiet) { _sawmill.Debug($"replacing {fileName} for {type}"); } var impl = _jitDelegate(type, uri, fileName, fileContent); if (impl != null) { _populateImplementations[type] = impl; } _fileContent[fileName] = fileContent; return impl; } /// /// Call the JITed implementation of Populate on a XAML-associated object . /// /// If no JITed implementation exists, return false. /// /// the static type of /// an instance of (can be a subclass) /// true if a JITed implementation existed public bool Populate(Type t, object o) { if (!_populateImplementations.TryGetValue(t, out var implementation)) { // JIT if needed. implementation = CompileType(t); // pop out if we never JITed anything/couldn't JIT if (implementation == null) return false; } implementation.Invoke(null, [null, o]); return true; } } #endif