types and deparsing and recalculation part working
[scpubgit/DX.git] / lib / DX / DependencyMap.pm
1 package DX::DependencyMap;
2
3 use DX::Utils qw(CONTENTS_OF INDICES_OF);
4 use DX::Class;
5
6 # { x => [ { y => [ ...
7 # my $targ = $root; $targ = $targ->[0]{$_[0]} for @path
8 # my $deps = $targ->[$${$dep_type}];
9
10 has deps => (is => 'ro', isa => DependencyTree, required => 1);
11
12 has revdeps => (is => 'ro', required => 1);
13
14 sub new_empty {
15   my ($class) = @_;
16   $class->new(deps => {}, revdeps => {});
17 }
18
19 sub with_entry_for {
20   my ($self, $for_id, $deps_for) = @_;
21   my @expanded = $self->_expand_deps($deps_for);
22   my $new_revdeps = {
23     %{$self->revdeps},
24     $for_id => \@expanded,
25   };
26   my $new_deps = $self->_merge_deps_for($self->deps, $for_id, @expanded);
27   ref($self)->new(
28     deps => $new_deps,
29     revdeps => $new_revdeps
30   );
31 }
32
33 sub without_entries_for {
34   my ($self, @for_ids) = @_;
35   my %new_revdeps = %{$self->revdeps};
36   my $new_deps = $self->deps;
37   $new_deps = $self->_unmerge_deps_for(
38     $new_deps, $_, @{$new_revdeps{$_}}
39   ) for @for_ids;
40   delete @new_revdeps{@for_ids};
41   ref($self)->new(
42     deps => $new_deps,
43     revdeps => \%new_revdeps
44   );
45 }
46
47 sub _merge_deps_for {
48   my ($self, $deps, $for_id, @merge_these) = @_;
49   $self->_mangle_deps($deps, sub {
50     +{ %{$_[0]}, $for_id => 1 };
51   }, @merge_these);
52 }
53
54 sub _unmerge_deps_for {
55   my ($self, $deps, $for_id, @unmerge_these) = @_;
56   $self->_mangle_deps($deps, sub {
57     my %for_ids = %{$_[0]};
58     delete $for_ids{$for_id};
59     \%for_ids;
60   }, @unmerge_these);
61 }
62
63 sub _mangle_deps {
64   my ($self, $deps, $mangler, @to_mangle) = @_;
65   my $root = [ $deps ];
66   foreach my $mangle_this (@to_mangle) {
67     assert_DependencySpec $mangle_this;
68     my ($type, @path) = @$mangle_this;
69     my $targ = $root;
70     foreach my $part (@path) {
71       my $sub = $targ->[0] = { %{$targ->[0]||{}} };
72       $targ = $sub->{$part} = [ @{$sub->{$part}||[]} ];
73     }
74     $targ->[$$$type] = $mangler->($targ->[$$$type]||{});
75   }
76   return $root->[0];
77 }
78
79 sub _expand_deps {
80   my ($self, $deps) = @_;
81   my @exp;
82   assert_DependencyGroupList $deps;
83   DEP: foreach my $dep (map @{$_}[1..$#$_], @$deps) {
84     my ($type, @path) = @$dep;
85     push @exp, [
86       $type,
87       map { ref() ? @{$_->identity_path or next DEP} : $_ } @path
88     ];
89   }
90   return @exp;
91 }
92
93 sub _dependents_of {
94   my ($self, $event) = @_;
95   my ($type, @path) = @$event;
96   my $root = [ $self->deps ];
97   my $targ = $root;
98   my $last = pop @path;
99   my @dep_sets;
100   foreach my $part (@path) {
101     $targ = $targ->[0]{$part};
102     # set/add/remove x.y or x.y.z affects contents of x
103     push @dep_sets, $targ->[$${+CONTENTS_OF}];
104   }
105   if ($$$type) {
106     # add/remove x.y affects indices of x
107     push @dep_sets, $targ->[$${+INDICES_OF}];
108   }
109   $targ = $targ->[0]{$last};
110   {
111     # add/remove x affects existence of x
112     # set/add/remove x affects everything else too
113     push @dep_sets, @{$targ}[map $$$_, @$$type];
114   }
115   # set/add/remove x affects all dependencies on x.y, x.y.z etc.
116   my @q = values %{$targ->[0]};
117   while (my $el = shift @q) {
118     my ($el_kids, @el_deps) = @$el;
119     push @dep_sets, @el_deps;
120     push @q, values %{$el_kids||{}};
121   }
122   return keys %{{
123     map +($_ => 1), map keys %$_, grep defined, @dep_sets
124   }};
125 }
126
127 sub but_expire_dependents_of {
128   my ($self, @events) = @_;
129   my @expired = keys %{{
130     map +($_ => 1), map $self->_dependents_of($_), @events
131   }};
132   # Didn't expire anything? Don't clone self
133   return $self unless @expired;
134   ($self->without_entries_for(@expired), @expired);
135 }
136
137 sub dependencies_for { $_[0]->revdeps->{$_[1]} }
138
139 1;