source: ps/trunk/source/tools/entity/Entity.pm@ 25223

Last change on this file since 25223 was 25223, checked in by wraitii, 3 years ago

Allow arbitrary compositions in TemplateLoader template names.

  • Allows compositing any two templates in TemplateLoader, and not just filters: 'A|B|C' is now valid for any template A, B and C.
  • Allows parents to be composited paths 'A|B|C'. In such a schema, if A or B themselves specify a parent, the actual composition becomes A|pA|B|pB|C and so on.

This allows, by leveraging the common schema of our entities, to reduce duplication.

For convenience, templates in "special/filters/" and "mixins/" can be included by their direct name. Others have to be completely specified.

See the two provided cases for examples:

  • 'hoplite' becomes a mixin that can be used to apply the Phalanx formation
  • 'builder' becomes a mixin that can be give a template the ability to build the standard structures, and gives the 'Builder' identity class. This also allows deduplicating that list of tokens.

Update checkrefs & swap std::map for std::unordered_map in TemplateLoader.

Differential Revision: https://code.wildfiregames.com/D3801

  • Property svn:eol-style set to native
File size: 4.6 KB
Line 
1package Entity;
2
3use strict;
4use warnings;
5
6use XML::Parser;
7use Data::Dumper;
8use File::Find;
9
10my $vfsroot = '../../../binaries/data/mods';
11
12sub get_filename
13{
14 my ($vfspath, $mod) = @_;
15 my $fn = "$vfsroot/$mod/simulation/templates/special/filter/$vfspath.xml";
16 if (not -e $fn) {
17 $fn = "$vfsroot/$mod/simulation/templates/mixins/$vfspath.xml";
18 }
19 if (not -e $fn) {
20 $fn = "$vfsroot/$mod/simulation/templates/$vfspath.xml";
21 }
22 return $fn;
23}
24
25sub get_file
26{
27 my ($vfspath, $mod) = @_;
28 my $fn = get_filename($vfspath, $mod);
29 open my $f, $fn or die "Error loading $fn: $!";
30 local $/;
31 return <$f>;
32}
33
34sub trim
35{
36 my ($t) = @_;
37 return '' if not defined $t;
38 $t =~ /^\s*(.*?)\s*$/s;
39 return $1;
40}
41
42sub load_xml
43{
44 my ($vfspath, $file) = @_;
45 my $root = {};
46 my @stack = ($root);
47 my $p = new XML::Parser(Handlers => {
48 Start => sub {
49 my ($e, $n, %a) = @_;
50 my $t = {};
51 die "Duplicate child node '$n'" if exists $stack[-1]{$n};
52 $stack[-1]{$n} = $t;
53 for (keys %a) {
54 $t->{'@'.$_}{' content'} = trim($a{$_});
55 }
56 push @stack, $t;
57 },
58 End => sub {
59 my ($e, $n) = @_;
60 $stack[-1]{' content'} = trim($stack[-1]{' content'});
61 pop @stack;
62 },
63 Char => sub {
64 my ($e, $str) = @_;
65 $stack[-1]{' content'} .= $str;
66 },
67 });
68 eval {
69 $p->parse($file);
70 };
71 if ($@) {
72 die "Error parsing $vfspath: $@";
73 }
74 return $root;
75}
76
77sub apply_layer
78{
79 my ($base, $new) = @_;
80 if ($new->{'@datatype'} and $new->{'@datatype'}{' content'} eq 'tokens') {
81 my @old = split /\s+/, ($base->{' content'} || '');
82 my @new = split /\s+/, ($new->{' content'} || '');
83 my @t = @old;
84 for my $n (@new) {
85 if ($n =~ /^-(.*)/) {
86 @t = grep $_ ne $1, @t;
87 } else {
88 push @t, $n if not grep $_ eq $n, @t;
89 }
90 }
91 $base->{' content'} = join ' ', @t;
92 } elsif ($new->{'@op'}) {
93 my $op = $new->{'@op'}{' content'};
94 my $op1 = $base->{' content'};
95 my $op2 = $new->{' content'};
96 if ($op eq 'add') {
97 $base->{' content'} = $op1 + $op2;
98 }
99 elsif ($op eq 'mul') {
100 $base->{' content'} = $op1 * $op2;
101 }
102 else {
103 die "Invalid operator '$op'";
104 }
105 } else {
106 $base->{' content'} = $new->{' content'};
107 }
108 for my $k (grep $_ ne ' content', keys %$new) {
109 if ($new->{$k}{'@disable'}) {
110 delete $base->{$k};
111 } else {
112 if ($new->{$k}{'@replace'}) {
113 delete $base->{$k};
114 }
115 $base->{$k} ||= {};
116 apply_layer($base->{$k}, $new->{$k});
117 delete $base->{$k}{'@replace'};
118 }
119 }
120}
121
122sub get_main_mod
123{
124 my ($vfspath, $mods) = @_;
125 my @mods_list = split(/\|/, $mods);
126 my $main_mod = $mods_list[0];
127 my $fn = "$vfsroot/$main_mod/simulation/templates/$vfspath.xml";
128 if (not -e $fn)
129 {
130 for my $dep (@mods_list)
131 {
132 $fn = "$vfsroot/$dep/simulation/templates/$vfspath.xml";
133 if (-e $fn)
134 {
135 $main_mod = $dep;
136 last;
137 }
138 }
139 }
140 return $main_mod;
141}
142
143sub load_inherited
144{
145 my ($vfspath, $mods, $base) = @_;
146 if ($vfspath =~ /\|/) {
147 my @paths = split(/\|/, $vfspath, 2);
148 $base = load_inherited($paths[1], $mods, $base);
149 $base = load_inherited($paths[0], $mods, $base);
150 return $base
151 }
152 my $main_mod = get_main_mod($vfspath, $mods);
153 my $layer = load_xml($vfspath, get_file($vfspath, $main_mod));
154
155 if ($layer->{Entity}{'@parent'}) {
156 my $parent = load_inherited($layer->{Entity}{'@parent'}{' content'}, $mods, $base);
157 apply_layer($parent->{Entity}, $layer->{Entity});
158 return $parent;
159 } else {
160 if (not $base) {
161 return $layer;
162 }
163 else {
164 apply_layer($base->{Entity}, $layer->{Entity});
165 return $base
166 }
167 }
168}
169
170sub find_entities
171{
172 my ($modName) = @_;
173 my @files;
174 my $find_process = sub {
175 return $File::Find::prune = 1 if $_ eq '.svn';
176 my $n = $File::Find::name;
177 return if /~$/;
178 return unless -f $_;
179 $n =~ s~\Q$vfsroot\E/$modName/simulation/templates/~~;
180 $n =~ s/\.xml$//;
181 push @files, $n;
182 };
183 find({ wanted => $find_process }, "$vfsroot/$modName/simulation/templates");
184
185 return @files;
186}
Note: See TracBrowser for help on using the repository browser.