composite_delta.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import {createHash} from "crypto";
  2. import {Delta} from "./delta";
  3. export class CompositeDelta implements Delta {
  4. readonly deltas: Array<Delta>;
  5. readonly dependencies: Array<CompositeDelta>;
  6. readonly typedDependencies: Array<[CompositeDelta, string]>;
  7. readonly conflicts: Array<CompositeDelta>;
  8. readonly hash: Buffer;
  9. readonly description: string;
  10. // Do not call constructor directly. Instead, use CompositeLevel.createComposite
  11. constructor(deltas: Array<Delta>, dependencies: Array<CompositeDelta>, typedDependencies: Array<[CompositeDelta,string]>, conflicts: Array<CompositeDelta>, hash: Buffer, description: string) {
  12. this.deltas = deltas;
  13. this.dependencies = dependencies;
  14. this.typedDependencies = typedDependencies;
  15. this.conflicts = conflicts;
  16. this.hash = hash;
  17. this.description = description;
  18. }
  19. getDependencies(): Array<CompositeDelta> {
  20. return this.dependencies;
  21. }
  22. getTypedDependencies(): Array<[CompositeDelta, string]> {
  23. return this.typedDependencies;
  24. }
  25. getConflicts(): Array<CompositeDelta> {
  26. return this.conflicts;
  27. }
  28. getHash(): Buffer {
  29. return this.hash;
  30. }
  31. getDescription(): string {
  32. return this.description;
  33. }
  34. serialize(): any {
  35. return {
  36. type: "CompositeDelta",
  37. deltas: this.deltas.map(d => d.serialize()),
  38. }
  39. }
  40. *iterPrimitiveDeltas(): Iterable<Delta> {
  41. for (const d of this.deltas) {
  42. yield* d.iterPrimitiveDeltas();
  43. }
  44. }
  45. }
  46. // A "registry" of composite deltas.
  47. // When creating a new CompositeDelta, it will figure out what other CompositeDeltas to depend on, and to conflict with.
  48. export class CompositeLevel {
  49. containedBy: Map<Delta, CompositeDelta> = new Map();
  50. createComposite(deltas: Array<Delta>, description: string = deltas.map(d=>d.getDescription()).join(",")): CompositeDelta {
  51. const dependencies: Array<CompositeDelta> = [];
  52. const typedDependencies: Array<[CompositeDelta,string]> = [];
  53. const conflicts: Array<CompositeDelta> = [];
  54. for (const delta of deltas) {
  55. if (this.containedBy.has(delta)) {
  56. throw new Error("Assertion failed: delta " + delta.getDescription() + " already part of another composite");
  57. }
  58. for (const [dependency, dependencyType] of delta.getTypedDependencies()) {
  59. if (!deltas.includes(dependency)) {
  60. // We got ourselves an inter-composite dependency.
  61. const compositeDependency = this.containedBy.get(dependency);
  62. if (compositeDependency === undefined) {
  63. throw new Error("Assertion failed: delta " + delta.getDescription() + " depends on " + dependency.getDescription() + " but this dependency could not be found in a composite.");
  64. }
  65. const existingDependency = typedDependencies.find(([dep,_]) => dep === compositeDependency);
  66. if (existingDependency !== undefined) {
  67. // existingDependency[1] += ","+dependencyType;
  68. } else {
  69. dependencies.push(compositeDependency);
  70. // typedDependencies.push([compositeDependency, dependencyType]);
  71. typedDependencies.push([compositeDependency, ""]);
  72. }
  73. }
  74. }
  75. for (const conflict of delta.getConflicts()) {
  76. if (deltas.includes(conflict)) {
  77. console.log("Conflict between", conflict, "and", delta);
  78. throw new Error("Cannot create a composite delta out of conflicting deltas");
  79. }
  80. const compositeConflict = this.containedBy.get(conflict);
  81. if (compositeConflict === undefined) {
  82. // We used to treat this as an error, however, it's possible that a conflicting delta simply isn't part yet of a composite delta...
  83. // throw new Error("Assertion failed: cannot find composite of " + conflict.getDescription());
  84. } else {
  85. if (!conflicts.includes(compositeConflict)) {
  86. conflicts.push(compositeConflict);
  87. }
  88. }
  89. }
  90. }
  91. if (dependencies.some(dependency => conflicts.includes(dependency))) {
  92. throw new Error("Assertion failed: Attempted to create a composite delta that conflicts with one of its dependencies.");
  93. }
  94. const hash = createHash('sha256');
  95. for (const delta of deltas) {
  96. hash.update(delta.getHash());
  97. }
  98. const composite = new CompositeDelta(deltas, dependencies, typedDependencies, conflicts, hash.digest(), description);
  99. for (const delta of deltas) {
  100. this.containedBy.set(delta, composite);
  101. }
  102. for (const compositeConflict of conflicts) {
  103. // Conflicts are symmetric, so newly created conflicts are also added to the other:
  104. compositeConflict.conflicts.push(composite);
  105. }
  106. return composite;
  107. }
  108. }