member_at starting to work
[scpubgit/DX.git] / lib / DX / DependencyMap.pm
CommitLineData
efad53c4 1package DX::DependencyMap;
2
3use DX::Utils qw(CONTENTS_OF INDICES_OF);
4use Moo;
5
6# { x => [ { y => [ ...
7# my $targ = $root; $targ = $targ->[0]{$_[0]} for @path
8# my $deps = $targ->[$${$dep_type}];
9
10has deps => (is => 'ro', required => 1);
11
12has revdeps => (is => 'ro', required => 1);
13
14sub new_empty {
15 my ($class) = @_;
16 $class->new(deps => {}, revdeps => {});
17}
18
19sub 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
33sub 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
47sub _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
54sub _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
63sub _mangle_deps {
64 my ($self, $deps, $mangler, @to_mangle) = @_;
65 my $root = [ $deps ];
66 foreach my $mangle_this (@to_mangle) {
67 my ($type, @path) = @$mangle_this;
68 my $targ = $root;
69 foreach my $part (@path) {
70 my $sub = $targ->[0] = { %{$targ->[0]||{}} };
71 $targ = $sub->{$part} = [ @{$sub->{$part}||[]} ];
72 }
73 $targ->[$$$type] = $mangler->($targ->[$$$type]||{});
74 }
75 return $root->[0];
76}
77
78sub _expand_deps {
79 my ($self, $deps) = @_;
80 my @exp;
0498469a 81 DEP: foreach my $dep (map @{$_}[1..$#$_], @$deps) {
efad53c4 82 my ($type, @path) = @$dep;
83 push @exp, [
84 $type,
85 map { ref() ? @{$_->identity_path or next DEP} : $_ } @path
86 ];
87 }
88 return @exp;
89}
90
91sub _dependents_of {
92 my ($self, $event) = @_;
93 my ($type, @path) = @$event;
94 my $root = [ $self->deps ];
95 my $targ = $root;
96 my $last = pop @path;
97 my @dep_sets;
98 foreach my $part (@path) {
99 $targ = $targ->[0]{$part};
100 # set/add/remove x.y or x.y.z affects contents of x
101 push @dep_sets, $targ->[$${+CONTENTS_OF}];
102 }
103 if ($$$type) {
104 # add/remove x.y affects indices of x
105 push @dep_sets, $targ->[$${+INDICES_OF}];
106 }
107 $targ = $targ->[0]{$last};
108 {
109 # add/remove x affects existence of x
110 # set/add/remove x affects everything else too
111 push @dep_sets, @{$targ}[map $$$_, @$$type];
112 }
113 # set/add/remove x affects all dependencies on x.y, x.y.z etc.
114 my @q = values %{$targ->[0]};
115 while (my $el = shift @q) {
116 my ($el_kids, @el_deps) = @$el;
117 push @dep_sets, @el_deps;
118 push @q, values %{$el_kids||{}};
119 }
120 return keys %{{
121 map +($_ => 1), map keys %$_, grep defined, @dep_sets
122 }};
123}
124
125sub but_expire_dependents_of {
126 my ($self, @events) = @_;
127 my @expired = keys %{{
128 map +($_ => 1), map $self->_dependents_of($_), @events
129 }};
130 # Didn't expire anything? Don't clone self
131 return $self unless @expired;
132 ($self->without_entries_for(@expired), @expired);
133}
134
1351;