Skip to content

Commit

Permalink
Merge (#9903)
Browse files Browse the repository at this point in the history
  • Loading branch information
noencke authored Apr 14, 2022
1 parent c99df9c commit be620da
Show file tree
Hide file tree
Showing 13 changed files with 313 additions and 303 deletions.
199 changes: 156 additions & 43 deletions experimental/dds/tree/README.md

Large diffs are not rendered by default.

155 changes: 0 additions & 155 deletions experimental/dds/tree/docs/Future.md

This file was deleted.

19 changes: 19 additions & 0 deletions experimental/dds/tree/docs/Write-Format.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# SharedTree Write Format

A SharedTree is given a **write format** upon creation. The write format dictates the scheme used to encode ops sent by the DDS as well as its summary format. Future write formats cannot be interpreted by SharedTrees that only understand past write formats and therefore care must be taken when migrating to a new format (e.g. the rollout of a new format must be delayed/staged until all clients support the new write format).

## Document Upgrade

SharedTree will automatically upgrade documents to a newer write format when necessary. This happens when a client with a newer write format loads a document written in an older write format. The joining client will upgrade the document to the new format as well as notify the other clients to change automatically to the newer write format.

> After a document upgrade occurs, clients that do not support the new write format will no longer be able to read the document!
## Write Formats

### 0.0.2

The first released write format. Ops and summaries are not compressed or optimized.

### 0.1.1

An optimized encoding is used for ops and summaries to dramatically reduce their serialized size. See the [0.1.1 compression document](./Compression.md) for details.
23 changes: 9 additions & 14 deletions experimental/dds/tree/src/ChangeTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,25 @@ export enum ChangeType {
*
* `Change` objects can be conveniently constructed with the helper methods exported on a constant of the same name.
* @example
* TreeChange.insert(sourceId, destination)
* Change.insert(sourceId, destination)
* @public
*/
export type Change = Insert | Detach | Build | SetValue | Constraint;

/**
* Node or sequence of Nodes for use in a Build change.
*
* Other formats for sub-sequences of Nodes can be added here, and those formats should be supported in blobs as well.
* Future formats will include referenced blobs containing sequences of Nodes,
* template based metadata and identity deduplication, and possibly compressed and binary formats.
* These optimized formats should also be used within tree views.
* Node or a detached sequence of nodes (referred to by a detached sequence ID) for use in a Build change.
* See `BuildTreeNode` for more.
* @public
*/
export type BuildNode = BuildTreeNode | number;

/**
* Node for use in a Build change.
* Node for use in a Build change, which is composed of a definition describing what this nodes type, an identifier identifying this node
* within the tree, and a payload containing an opaque serializable piece of data.
* An identifier can be provided explicitly if the node must be referred to before the results of the `Change` containing this
* BuildTreeNode can be observed. If `identifier` is not supplied, one will be generated for it in an especially efficient manner
* that allows for compact storage and transmission and thus this property should be omitted if convenient.
* See the SharedTree readme for more on the tree format.
*/
export interface BuildTreeNode extends HasVariadicTraits<BuildNode> {
definition: string;
Expand All @@ -68,12 +69,6 @@ export interface BuildTreeNode extends HasVariadicTraits<BuildNode> {
* Valid if (transitively) all DetachedSequenceId are used according to their rules (use here counts as a destination),
* and all Nodes' identifiers are previously unused.
*
* TODO: Design Decision:
* If allowing 'moving from nowhere' to restore nodes: all new Nodes must have never before used identifiers.
* Otherwise could just forbid identifiers currently reachable?
* Could also allow introducing a node with a particular identifier to mean replacing that node with the new one
* (could include optional constraint to require/prevent this).
*
* @public
*/
export interface Build {
Expand Down
6 changes: 3 additions & 3 deletions experimental/dds/tree/src/Checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { CachingLogViewer } from './LogViewer';
import { TreeView } from './TreeView';
import { RevisionView } from './RevisionView';
import { EditCommittedHandler, SharedTree } from './SharedTree';
import { GenericTransaction, ValidEditingResult } from './TransactionInternal';
import { GenericTransaction, TransactionInternal, ValidEditingResult } from './TransactionInternal';
import { ChangeInternal, Edit, EditStatus } from './persisted-types';
import { SharedTreeEvent } from './EventTypes';
import { newEditId } from './EditUtilities';
Expand Down Expand Up @@ -153,7 +153,7 @@ export abstract class Checkout extends EventEmitterWithErrorHandling<ICheckoutEv
*/
public openEdit(): void {
assert(this.currentEdit === undefined, 'An edit is already open.');
this.currentEdit = this.tree.transactionFactory(this.latestCommittedView);
this.currentEdit = TransactionInternal.factory(this.latestCommittedView);
}

/**
Expand Down Expand Up @@ -275,7 +275,7 @@ export abstract class Checkout extends EventEmitterWithErrorHandling<ICheckoutEv
// When closed, the result might indicate Malformed due to unused detached entities.
// This is not an error, as the edit was still open and can still use those entities.
const priorResults = this.currentEdit.close();
const rebasedEdit = this.tree.transactionFactory(this.latestCommittedView).applyChanges(priorResults.changes);
const rebasedEdit = TransactionInternal.factory(this.latestCommittedView).applyChanges(priorResults.changes);
assert(
rebasedEdit.status !== EditStatus.Malformed,
'Malformed changes should have been caught on original application.'
Expand Down
8 changes: 4 additions & 4 deletions experimental/dds/tree/src/LogViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Revision, RevisionValueCache } from './RevisionValueCache';
import { ReconciliationChange, ReconciliationEdit, ReconciliationPath } from './ReconciliationPath';
import { ChangeInternal, Edit, EditStatus } from './persisted-types';
import { RevisionView } from './RevisionView';
import { EditingResult, TransactionInternal, TransactionFactory } from './TransactionInternal';
import { EditingResult, TransactionInternal } from './TransactionInternal';

/**
* Callback for when an edit is applied (meaning the result of applying it to a particular revision is computed).
Expand Down Expand Up @@ -261,7 +261,6 @@ export class CachingLogViewer implements LogViewer {
expensiveValidation = false,
processEditStatus: EditStatusCallback = noop,
processSequencedEditResult: SequencedEditResultCallback = noop,
private readonly transactionFactory: TransactionFactory,
minimumSequenceNumber = 0
) {
this.log = log;
Expand All @@ -283,7 +282,6 @@ export class CachingLogViewer implements LogViewer {
this.processEditStatus = processEditStatus ?? noop;
this.processSequencedEditResult = processSequencedEditResult ?? noop;
this.expensiveValidation = expensiveValidation;
this.transactionFactory = transactionFactory;
this.detachFromEditLog = this.log.registerEditAddedHandler(this.handleEditAdded.bind(this));
}

Expand Down Expand Up @@ -446,7 +444,9 @@ export class CachingLogViewer implements LogViewer {
cached = true;
} else {
reconciliationPath = this.reconciliationPathFromEdit(edit.id);
editingResult = this.transactionFactory(prevView).applyChanges(edit.changes, reconciliationPath).close();
editingResult = TransactionInternal.factory(prevView)
.applyChanges(edit.changes, reconciliationPath)
.close();
cached = false;
}

Expand Down
37 changes: 26 additions & 11 deletions experimental/dds/tree/src/NodeIdUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,15 @@ export interface NodeIdContext extends NodeIdGenerator, NodeIdConverter {}
*/
export interface NodeIdGenerator {
/**
* Generate an identifier that may be used for a new node that will be inserted into this tree
* @param override - an optional string ID to associate with the new id for future lookup. The same override always returns the same ID.
* Generates a node identifier.
* The returned IDs may be used as the identifier of a node in the SharedTree.
* `NodeId`s are *always* unique and stable within the scope of the tree and session that generated them. They are *not* unique within
* a Fluid container, and *cannot* be compared across instances of a SharedTree. They are *not* stable across sessions/lifetimes of a
* SharedTree, and *cannot* be persisted (e.g. stored in payloads, uploaded in blobs, etc.). If stable persistence is needed,
* NodeIdConverter.convertToStableNodeId may be used to return a corresponding UUID that is globally unique and stable.
* @param override - if supplied, calls to `convertToStableNodeId` using the returned node ID will return the override instead of
* the UUID. Calls to `generateNodeId` with the same override always return the same ID. Performance note: passing an override string
* incurs a storage cost that is significantly higher that a node ID without one, and should be avoided if possible.
*/
generateNodeId(override?: string): NodeId;
}
Expand All @@ -32,27 +39,35 @@ export interface NodeIdGenerator {
*/
export interface NodeIdConverter {
/**
* Given a NodeId, return the corresponding UUID. The result is safe to persist and re-use across `SharedTree` instances, unlike NodeId
* Given a NodeId, returns the corresponding stable ID or throws if the supplied node ID was not generated with this tree (`NodeId`s
* may not be used across SharedTree instances, see `generateNodeId` for more).
* The returned value will be a UUID, unless the creation of `id` used an override string (see `generateNodeId` for more).
* The result is safe to persist and re-use across `SharedTree` instances, unlike `NodeId`.
*/
convertToStableNodeId(id: NodeId): StableNodeId;

/**
* Given a NodeId, attempt to return the corresponding UUID.
* The returned UUID is undefined if no such ID was ever created. If a UUID is returned, it is not guaranteed to be in the current
* revision (but it is guaranteed to exist in at least one prior revision).
* Given a NodeId, attempt to return the corresponding stable ID.
* The returned value will be a UUID, unless the creation of `id` used an override string (see `generateNodeId` for more).
* The returned stable ID is undefined if `id` was never created with this SharedTree. If a stable ID is returned, this does not imply
* that there is a node with `id` in the current revision of the tree, only that `id` was at some point generated by some instance of
* this tree.
*/
tryConvertToStableNodeId(id: NodeId): StableNodeId | undefined;

/**
* Given an UUID, return the corresponding NodeId.
* The returned NodeId is not guaranteed to be in the current revision (but it is guaranteed to exist in at least one prior revision).
* Given a stable ID, return the corresponding NodeId or throws if the supplied stable ID was never generated with this tree, either
* as a UUID corresponding to a `NodeId` or as an override passed to `generateNodeId`.
* If a stable ID is returned, this does not imply that there is a node with `id` in the current revision of the tree, only that
* `id` was at some point generated by an instance of this SharedTree.
*/
convertToNodeId(id: StableNodeId): NodeId;

/**
* Given an UUID, attempt to return the corresponding NodeId.
* The returned NodeId is undefined if no such ID was ever created. If a NodeId is returned, it is not guaranteed to be in the current
* revision (but it is guaranteed to exist in at least one prior revision).
* Given a stable ID, return the corresponding NodeId or return undefined if the supplied stable ID was never generated with this tree,
* either as a UUID corresponding to a `NodeId` or as an override passed to `generateNodeId`.
* If a stable ID is returned, this does not imply that there is a node with `id` in the current revision of the tree, only that
* `id` was at some point generated by an instance of this SharedTree.
*/
tryConvertToNodeId(id: StableNodeId): NodeId | undefined;
}
Expand Down
Loading

0 comments on commit be620da

Please sign in to comment.