LiveElement CreateChildElement(SourceFragment sourceFragment) { var elm = new LiveElement(parent: this); elm.UpdateFrom(sourceFragment.ToXml()); return(elm); }
public LiveElement(LiveElement parent) : this() { _parent = parent; _invalidated = parent._invalidated; _mutations = parent._mutations; _metadata = parent._metadata; _isReadOnly = parent._isReadOnly; _getElement = parent._getElement; _textPosition = new BehaviorSubject <SourceReference>(new SourceReference(parent._textPosition.Value.File, Optional.None())); Init(); }
public LiveProject(Fusion.IDocument fusionDocument, IFileSystem shell, IObservable <BytecodeGenerated> bytecodeGenerated, IScheduler scheduler = null) { scheduler = scheduler ?? Scheduler.Default; _shell = shell; var mutations = new Subject <IBinaryMessage>(); var idToElement = new BehaviorSubject <Dictionary <ObjectIdentifier, IElement> >(new Dictionary <ObjectIdentifier, IElement>()); var metadata = bytecodeGenerated .Select(bc => bc.Bytecode.Metadata.ElementTypeHierarchy.ToLookup()) .StartWith(Lookup.Empty <ObjectIdentifier, ObjectIdentifier>()) .Replay(1); _idToElement = idToElement; metadata.Connect(); bytecodeGenerated.Subscribe(bc => { var elements = idToElement.Value; foreach (var type in bc.Bytecode.Metadata.PrecompiledElements) { IElement element; if (!elements.TryGetValue(type.Id, out element)) { elements[type.Id] = element = new LiveElement( AbsoluteFilePath.Parse("N/A"), metadata, Observable.Return(true), new Subject <Unit>(), Observer.Create <IBinaryMessage>(_ => { }), getElement: GetElement); } element.Replace(_ => Task.FromResult(SourceFragment.FromString(type.Source))).Wait(); } idToElement.OnNext(elements); }); var unoProj = ProjectWatcher.Create(fusionDocument, shell, scheduler); Name = unoProj.Name; PackageReferences = unoProj.PackageReferences; ProjectReferences = unoProj.ProjectReferences; var liveDocuments = unoProj.UxFiles .CachePerElement(e => LiveDocument.Open(e, shell, metadata, idToElement, mutations, scheduler), val => val.Dispose()) .Select(ImmutableList.CreateRange) .Replay(1).RefCount(); var filePath = fusionDocument.FilePath.NotNone(); FilePath = filePath; RootDirectory = filePath.Select(f => f.ContainingDirectory); BuildOutputDirectory = unoProj.BuildOutputDirectory; _liveDocuments = liveDocuments; Mutations = mutations; BundleFiles = unoProj.BundleFiles; FuseJsFiles = unoProj.FuseJsFiles; Documents = liveDocuments.Select(d => ImmutableList.ToImmutableList(d.Cast <IDocument>())); var allElements = Documents .CachePerElement(doc => doc.Elements) .Select(docs => docs.ToObservableEnumerable()).Switch() .Select(e => e.Join().ToArray()) .Replay(1).RefCount(); var classes = allElements .Where(e => e.HasProperty("ux:Class")) .DistinctUntilSetChanged() .Replay(1); var globals = allElements .Where(e => e.HasProperty("ux:Global")) .DistinctUntilSetChanged() .Replay(1); Classes = classes; GlobalElements = globals; LogMessages = _liveDocuments.Switch(docs => docs.Select(doc => doc.Errors.NotNone().Select(o => doc.SimulatorIdPrefix + ": " + o.Message)) .Merge()); _garbage = Disposable.Combine( classes.Connect(), globals.Connect(), Disposable.Create(() => liveDocuments .FirstAsync() .Subscribe(documents => documents.Each(d => d.Dispose())))); var app = Documents .SelectPerElement(d => d.Root) .WherePerElement(e => e.Name.Is("App")) .Select(e => e.FirstOr(Element.Empty)) .Switch(); Context = new Context(app, GetElement); }
void UpdateChildren(XElement newElement) { var oldChildren = _children.Value; var newChildren = newElement.Elements().ToList(); if (oldChildren.Count == 0 && newChildren.Count == 0) { return; } var builder = new LiveElement[newChildren.Count]; // We'll be using newChildren as a worklist, so let's take a note of the position of all the new children var newChildrenLocation = newElement.Elements() .Select((e, i) => Tuple.Create(e, i)) .ToDictionary(t => t.Item1, t => t.Item2); var needsReify = newChildren.Count != oldChildren.Count; // Incrementally (eagerly) find a new child with same element type // Performance should be "okay" since we're scanning both lists from the start and removing children from the worklist as we find them foreach (var oldChildImpl in oldChildren) { foreach (var newChild in newChildren) { // If one of the new XElement children we got has the same name as one of our IElements if (oldChildImpl.Element.Name.LocalName == newChild.Name.LocalName) { // Update the old IElement builder[newChildrenLocation[newChild]] = oldChildImpl; oldChildImpl.UpdateFrom(newChild); // Remove the new child from the worklist and stop iterating over it newChildren.Remove(newChild); // Breaking the loop here is important both for correctness and to avoid iterating over a changed collection break; } // elements will be removed needsReify = true; } } needsReify |= newChildren.Any(); // So, we've reused all the elements we can reuse, but we still might have some left in our newChildren worklist foreach (var newChild in newChildren) { // Fortunately we know where they go var index = newChildrenLocation[newChild]; // Since we're iterating through them from start to end the index should not be out of bounds var childElement = CreateChildElement(SourceFragment.FromString("<" + newChild.Name.LocalName + "/>")); childElement.UpdateFrom(newChild); builder[index] = childElement; } if (needsReify) { _children.OnNext(ImmutableList.Create(builder)); _mutations.OnNext(new ReifyRequired()); } }
public static LiveDocument Open( AbsoluteFilePath path, IFileSystem fs, IObservable <ILookup <ObjectIdentifier, ObjectIdentifier> > metadata, BehaviorSubject <Dictionary <ObjectIdentifier, IElement> > idToElement, IObserver <IBinaryMessage> mutations, IScheduler scheduler) { IDocument <byte[]> fileOnDisk = new FileWatchingDocument(fs, path); var parsingErrors = new BehaviorSubject <Optional <Exception> >(Optional.None()); var invalidated = new Subject <Unit>(); var root = new LiveElement( file: path, metadata: metadata, isReadOnly: fileOnDisk.ErrorsDuringLoading.Or(parsingErrors) .Select(e => e.HasValue) .DistinctUntilChanged() .Replay(1).RefCount(), invalidated: invalidated, mutations: mutations, getElement: id => idToElement.Value.TryGetValue(id).Or(Element.Empty)); Optional <XElement> xElementForSaving = Optional.None <XElement>(); var allElements = root.Subtree().Replay(1); var source = new ReplaySubject <SourceFragment>(1); return(new LiveDocument { _garbage = Disposable.Combine( // Save on internal changes invalidated .Select(_ => { if (xElementForSaving.HasValue) { var sourceFragment = SourceFragment.FromXml(xElementForSaving.Value); source.OnNext(sourceFragment); return Optional.Some(sourceFragment); } return Optional.None(); }) .NotNone() .Throttle(TimeSpan.FromSeconds(0.5), scheduler) .Select(sourceFragment => Observable.FromAsync(async() => await fileOnDisk.Save(sourceFragment.ToBytes()))) .Concat() .Subscribe(), // Load on external changes fileOnDisk.ExternalChanges .ObserveOn(Application.MainThread) .Subscribe(reload => { var sourceFragment = SourceFragment.FromBytes(reload); source.OnNext(sourceFragment); try { var simulatorWasFaulted = parsingErrors.Value.HasValue; var newDocument = sourceFragment.ToXml(); parsingErrors.OnNext(Optional.None()); xElementForSaving = Optional.None(); Console.WriteLine("Reloading " + path + " from disk..."); root.UpdateFrom(newDocument); // no known reasons to throw xElementForSaving = Optional.Some(newDocument); // hack to clear errors from the simulator, // since UpdateFrom() doesn't know that the simulator // have failed and will try to emit incremental updates if (simulatorWasFaulted) { mutations.OnNext(new ReifyRequired()); } } catch (Exception e) { parsingErrors.OnNext(e); // hack to get errors from the simulator mutations.OnNext(new ReifyRequired()); } }), // Share subscription to Eleements allElements.Connect(), // Dispose fileOnDisk when disposed fileOnDisk), FilePath = Observable.Return(path), Errors = fileOnDisk.Errors.Or(parsingErrors), SimulatorIdPrefix = path.NativePath, Source = source, Root = root, Elements = allElements, _root = root, _path = path, _idToElement = idToElement, }); }