LiveElement CreateChildElement(SourceFragment sourceFragment)
        {
            var elm = new LiveElement(parent: this);

            elm.UpdateFrom(sourceFragment.ToXml());
            return(elm);
        }
Пример #2
0
        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();
        }
Пример #3
0
        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);
        }
Пример #4
0
        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());
            }
        }
Пример #5
0
        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,
            });
        }