Rev 1221 | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1033 | werner | 1 | /******************************************************************************************** |
2 | ** iLand - an individual based forest landscape and disturbance model |
||
3 | ** http://iland.boku.ac.at |
||
4 | ** Copyright (C) 2009- Werner Rammer, Rupert Seidl |
||
5 | ** |
||
6 | ** This program is free software: you can redistribute it and/or modify |
||
7 | ** it under the terms of the GNU General Public License as published by |
||
8 | ** the Free Software Foundation, either version 3 of the License, or |
||
9 | ** (at your option) any later version. |
||
10 | ** |
||
11 | ** This program is distributed in the hope that it will be useful, |
||
12 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
13 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
14 | ** GNU General Public License for more details. |
||
15 | ** |
||
16 | ** You should have received a copy of the GNU General Public License |
||
17 | ** along with this program. If not, see <http://www.gnu.org/licenses/>. |
||
18 | ********************************************************************************************/ |
||
884 | werner | 19 | #include "global.h" |
908 | werner | 20 | #include "abe_global.h" |
884 | werner | 21 | #include "fmtreelist.h" |
22 | |||
23 | #include "forestmanagementengine.h" |
||
24 | // iLand stuff |
||
25 | #include "tree.h" |
||
26 | #include "expression.h" |
||
27 | #include "mapgrid.h" |
||
885 | werner | 28 | #include "expressionwrapper.h" |
29 | #include "model.h" |
||
913 | werner | 30 | #include "helper.h" |
885 | werner | 31 | #include "fmstand.h" |
887 | werner | 32 | #include "fomescript.h" |
884 | werner | 33 | |
907 | werner | 34 | namespace ABE { |
884 | werner | 35 | |
1095 | werner | 36 | /** @class FMTreeList |
37 | @ingroup abe |
||
38 | The FMTreeList class implements low-level functionality for selecting and harvesting of trees. |
||
39 | The functions of the class are usually accessed via Javascript. |
||
40 | |||
41 | */ |
||
42 | |||
43 | |||
885 | werner | 44 | // TODO: fix: removal fractions need to be moved to agent/units/ whatever.... |
45 | double removeFoliage() {return 0.;} |
||
46 | double removeStem() {return 1.;} |
||
47 | double removeBranch() {return 0.;} |
||
48 | |||
884 | werner | 49 | FMTreeList::FMTreeList(QObject *parent) : |
50 | QObject(parent) |
||
51 | { |
||
932 | werner | 52 | mStand = 0; |
885 | werner | 53 | setStand(0); // clear stand link |
932 | werner | 54 | mResourceUnitsLocked = false; |
885 | werner | 55 | |
884 | werner | 56 | } |
57 | |||
891 | werner | 58 | FMTreeList::FMTreeList(FMStand *stand, QObject *parent): |
59 | QObject(parent) |
||
60 | { |
||
932 | werner | 61 | mStand = 0; |
891 | werner | 62 | setStand(stand); |
932 | werner | 63 | mResourceUnitsLocked = false; |
891 | werner | 64 | } |
65 | |||
932 | werner | 66 | FMTreeList::~FMTreeList() |
67 | { |
||
68 | check_locks(); |
||
69 | } |
||
70 | |||
889 | werner | 71 | void FMTreeList::setStand(FMStand *stand) |
885 | werner | 72 | { |
932 | werner | 73 | check_locks(); |
885 | werner | 74 | mStand = stand; |
75 | if (stand) { |
||
76 | mStandId = stand->id(); |
||
77 | mNumberOfStems = stand->stems() * stand->area(); |
||
891 | werner | 78 | mOnlySimulate = stand->currentActivity()?stand->currentFlags().isScheduled() : false; |
912 | werner | 79 | mStandRect=QRectF(); |
885 | werner | 80 | } else { |
81 | mStandId = -1; |
||
82 | mNumberOfStems = 1000; |
||
891 | werner | 83 | mOnlySimulate = false; |
885 | werner | 84 | } |
932 | werner | 85 | |
885 | werner | 86 | } |
87 | |||
88 | |||
912 | werner | 89 | |
884 | werner | 90 | int FMTreeList::load(const QString &filter) |
91 | { |
||
92 | if (standId()>-1) { |
||
93 | // load all trees of the current stand |
||
94 | const MapGrid *map = ForestManagementEngine::instance()->standGrid(); |
||
95 | if (map->isValid()) { |
||
885 | werner | 96 | map->loadTrees(mStandId, mTrees, filter, mNumberOfStems); |
932 | werner | 97 | mResourceUnitsLocked = true; |
884 | werner | 98 | } else { |
909 | werner | 99 | qCDebug(abe) << "FMTreeList::load: grid is not valid - no trees loaded"; |
884 | werner | 100 | } |
101 | return mTrees.count(); |
||
102 | |||
103 | } else { |
||
909 | werner | 104 | qCDebug(abe) << "FMTreeList::load: loading *all* trees, because stand id is -1"; |
884 | werner | 105 | TreeWrapper tw; |
106 | Model *m = GlobalSettings::instance()->model(); |
||
107 | mTrees.clear(); |
||
108 | AllTreeIterator at(m); |
||
109 | if (filter.isEmpty()) { |
||
110 | while (Tree *t=at.nextLiving()) |
||
111 | if (!t->isDead()) |
||
112 | mTrees.push_back(QPair<Tree*, double>(t, 0.)); |
||
113 | } else { |
||
114 | Expression expr(filter,&tw); |
||
115 | expr.enableIncSum(); |
||
116 | qDebug() << "filtering with" << filter; |
||
117 | while (Tree *t=at.nextLiving()) { |
||
118 | tw.setTree(t); |
||
119 | if (!t->isDead() && expr.execute()) |
||
120 | mTrees.push_back(QPair<Tree*, double>(t, 0.)); |
||
121 | } |
||
122 | } |
||
123 | return mTrees.count(); |
||
124 | } |
||
125 | } |
||
126 | |||
885 | werner | 127 | int FMTreeList::removeMarkedTrees() |
128 | { |
||
129 | loadAll(); |
||
130 | int n_removed = 0; |
||
131 | for (QVector<QPair<Tree*, double> >::const_iterator it = mTrees.constBegin(); it!=mTrees.constEnd(); ++it) { |
||
132 | Tree *t = const_cast<Tree*>((*it).first); |
||
133 | if (t->isMarkedForCut()) { |
||
134 | t->remove(); |
||
135 | n_removed++; |
||
136 | } else if (t->isMarkedForHarvest()) { |
||
137 | t->remove(removeFoliage(), removeBranch(), removeStem()); |
||
138 | n_removed++; |
||
139 | } |
||
140 | } |
||
888 | werner | 141 | if (mStand->trace()) |
909 | werner | 142 | qCDebug(abe) << mStand->context() << "removeMarkedTrees: n=" << n_removed; |
1062 | werner | 143 | |
144 | return n_removed; |
||
885 | werner | 145 | } |
884 | werner | 146 | |
1070 | werner | 147 | int FMTreeList::kill(QString filter) |
148 | { |
||
149 | return remove_trees(filter, 1., false); |
||
150 | } |
||
151 | |||
885 | werner | 152 | int FMTreeList::harvest(QString filter, double fraction) |
884 | werner | 153 | { |
885 | werner | 154 | return remove_trees(filter, fraction, true); |
155 | |||
884 | werner | 156 | } |
157 | |||
887 | werner | 158 | bool FMTreeList::trace() const |
159 | { |
||
160 | return FomeScript::bridge()->standObj()->trace(); |
||
161 | } |
||
884 | werner | 162 | |
887 | werner | 163 | |
885 | werner | 164 | int FMTreeList::remove_percentiles(int pctfrom, int pctto, int number, bool management) |
165 | { |
||
166 | if (mTrees.isEmpty()) |
||
167 | return 0; |
||
168 | int index_from = limit(int(pctfrom/100. * mTrees.count()), 0, mTrees.count()); |
||
169 | int index_to = limit(int(pctto/100. * mTrees.count()), 0, mTrees.count()-1); |
||
170 | if (index_from>=index_to) |
||
171 | return 0; |
||
923 | werner | 172 | //qDebug() << "attempting to remove" << number << "trees between indices" << index_from << "and" << index_to; |
885 | werner | 173 | int i; |
174 | int count = number; |
||
175 | if (index_to-index_from <= number) { |
||
176 | // kill all |
||
177 | if (management) { |
||
178 | // management |
||
179 | for (i=index_from; i<index_to; i++) |
||
889 | werner | 180 | if (simulate()) { |
885 | werner | 181 | mTrees.at(i).first->markForHarvest(true); |
889 | werner | 182 | mStand->addScheduledHarvest(mTrees.at(i).first->volume()); |
183 | } else { |
||
885 | werner | 184 | mTrees.at(i).first->remove(removeFoliage(), removeBranch(), removeStem()); |
889 | werner | 185 | } |
885 | werner | 186 | } else { |
187 | // just kill... |
||
188 | for (i=index_from; i<index_to; i++) |
||
889 | werner | 189 | if (simulate()) { |
885 | werner | 190 | mTrees.at(i).first->markForCut(true); |
889 | werner | 191 | mStand->addScheduledHarvest(mTrees.at(i).first->volume()); |
192 | } else |
||
885 | werner | 193 | mTrees.at(i).first->remove(); |
194 | } |
||
195 | count = index_to - index_from; |
||
196 | } else { |
||
197 | // kill randomly the provided number |
||
198 | int cancel = 1000; |
||
199 | while(number>=0) { |
||
200 | int rnd_index = irandom(index_from, index_to); |
||
923 | werner | 201 | Tree *tree = mTrees[rnd_index].first; |
202 | if (tree->isDead() || tree->isMarkedForHarvest() || tree->isMarkedForCut()) { |
||
885 | werner | 203 | if (--cancel<0) { |
204 | qDebug() << "Management::kill: canceling search." << number << "trees left."; |
||
205 | count-=number; // not all trees were killed |
||
206 | break; |
||
207 | } |
||
208 | continue; |
||
209 | } |
||
210 | cancel = 1000; |
||
211 | number--; |
||
212 | if (management) { |
||
889 | werner | 213 | if (simulate()) { |
923 | werner | 214 | tree->markForHarvest(true); |
215 | mStand->addScheduledHarvest( tree->volume()); |
||
889 | werner | 216 | } else |
923 | werner | 217 | tree->remove( removeFoliage(), removeBranch(), removeStem() ); |
885 | werner | 218 | } else { |
889 | werner | 219 | if (simulate()) { |
923 | werner | 220 | tree->markForCut(true); |
221 | mStand->addScheduledHarvest( tree->volume()); |
||
889 | werner | 222 | } else |
923 | werner | 223 | tree->remove(); |
885 | werner | 224 | } |
225 | } |
||
226 | } |
||
923 | werner | 227 | if (mStand && mStand->trace()) |
228 | qCDebug(abe) << "FMTreeList::remove_percentiles:" << count << "removed."; |
||
885 | werner | 229 | // clean up the tree list... |
230 | for (int i=mTrees.count()-1; i>=0; --i) { |
||
231 | if (mTrees[i].first->isDead()) |
||
232 | mTrees.removeAt(i); |
||
233 | } |
||
234 | return count; // killed or manages |
||
235 | |||
236 | } |
||
237 | |||
238 | /** remove trees from a list and reduce the list. |
||
239 | |||
240 | */ |
||
241 | int FMTreeList::remove_trees(QString expression, double fraction, bool management) |
||
242 | { |
||
243 | TreeWrapper tw; |
||
244 | if (expression.isEmpty()) |
||
245 | expression="true"; |
||
246 | Expression expr(expression,&tw); |
||
247 | expr.enableIncSum(); |
||
248 | int n = 0; |
||
249 | QVector<QPair<Tree*, double> >::iterator tp=mTrees.begin(); |
||
250 | try { |
||
251 | while (tp!=mTrees.end()) { |
||
252 | tw.setTree(tp->first); |
||
253 | // if expression evaluates to true and if random number below threshold... |
||
254 | if (expr.calculate(tw) && drandom() <=fraction) { |
||
255 | // remove from system |
||
256 | if (management) { |
||
889 | werner | 257 | if (simulate()) { |
885 | werner | 258 | tp->first->markForHarvest(true); |
889 | werner | 259 | mStand->addScheduledHarvest(tp->first->volume()); |
1070 | werner | 260 | } else { |
261 | tp->first->markForHarvest(true); |
||
885 | werner | 262 | tp->first->remove(removeFoliage(), removeBranch(), removeStem()); // management with removal fractions |
1070 | werner | 263 | } |
885 | werner | 264 | } else { |
889 | werner | 265 | if (simulate()) { |
885 | werner | 266 | tp->first->markForCut(true); |
1070 | werner | 267 | tp->first->setDeathCutdown(); |
889 | werner | 268 | mStand->addScheduledHarvest(tp->first->volume()); |
1070 | werner | 269 | } else { |
270 | tp->first->markForCut(true); |
||
271 | tp->first->setDeathCutdown(); |
||
885 | werner | 272 | tp->first->remove(); // kill |
1070 | werner | 273 | } |
885 | werner | 274 | } |
275 | // remove from tree list |
||
276 | tp = mTrees.erase(tp); |
||
277 | n++; |
||
278 | } else { |
||
279 | ++tp; |
||
280 | } |
||
281 | } |
||
282 | } catch(const IException &e) { |
||
909 | werner | 283 | qCWarning(abe) << "treelist: remove_trees: expression:" << expression << ", msg:" << e.message(); |
885 | werner | 284 | } |
285 | return n; |
||
286 | |||
287 | } |
||
288 | |||
289 | double FMTreeList::aggregate_function(QString expression, QString filter, QString type) |
||
290 | { |
||
291 | QVector<QPair<Tree*, double> >::iterator tp=mTrees.begin(); |
||
292 | TreeWrapper tw; |
||
293 | Expression expr(expression,&tw); |
||
294 | |||
295 | double sum = 0.; |
||
296 | int n=0; |
||
297 | try { |
||
298 | |||
299 | if (filter.isEmpty()) { |
||
300 | // without filtering |
||
301 | while (tp!=mTrees.end()) { |
||
302 | tw.setTree(tp->first); |
||
303 | sum += expr.calculate(); |
||
304 | ++n; |
||
305 | ++tp; |
||
306 | } |
||
307 | } else { |
||
308 | // with filtering |
||
309 | Expression filter_expr(filter,&tw); |
||
310 | filter_expr.enableIncSum(); |
||
311 | while (tp!=mTrees.end()) { |
||
312 | tw.setTree(tp->first); |
||
313 | if (filter_expr.calculate()) { |
||
314 | sum += expr.calculate(); |
||
315 | ++n; |
||
316 | } |
||
317 | ++tp; |
||
318 | } |
||
319 | } |
||
320 | |||
321 | } catch(const IException &e) { |
||
909 | werner | 322 | qCWarning(abe) << "Treelist: aggregate function: expression:" << expression << ", filter:" << filter << ", msg:" <<e.message(); |
885 | werner | 323 | //throwError(e.message()); |
324 | } |
||
325 | if (type=="sum") |
||
326 | return sum; |
||
327 | if (type=="mean") |
||
328 | return n>0?sum/double(n):0.; |
||
329 | return 0.; |
||
330 | |||
331 | } |
||
332 | |||
930 | werner | 333 | bool FMTreeList::remove_single_tree(int index, bool harvest) |
334 | { |
||
335 | if (!mStand || index<0 || index>=mTrees.size()) |
||
336 | return false; |
||
337 | Tree *tree = mTrees.at(index).first; |
||
338 | if (harvest) { |
||
339 | if (simulate()) { |
||
340 | tree->markForHarvest(true); |
||
341 | mStand->addScheduledHarvest( tree->volume()); |
||
342 | } else |
||
343 | tree->remove( removeFoliage(), removeBranch(), removeStem() ); |
||
344 | } else { |
||
345 | if (simulate()) { |
||
346 | tree->markForCut(true); |
||
347 | mStand->addScheduledHarvest( tree->volume()); |
||
348 | } else |
||
349 | tree->remove(); |
||
350 | } |
||
351 | return true; |
||
352 | } |
||
923 | werner | 353 | |
930 | werner | 354 | |
923 | werner | 355 | bool treePairValue(const QPair<Tree*, double> &p1, const QPair<Tree*, double> &p2) |
356 | { |
||
357 | return p1.second < p2.second; |
||
358 | } |
||
359 | |||
360 | void FMTreeList::sort(QString statement) |
||
361 | { |
||
362 | TreeWrapper tw; |
||
363 | Expression sorter(statement, &tw); |
||
364 | // fill the "value" part of the tree storage with a value for each tree |
||
365 | for (int i=0;i<mTrees.count(); ++i) { |
||
366 | tw.setTree(mTrees.at(i).first); |
||
367 | mTrees[i].second = sorter.execute(); |
||
368 | } |
||
369 | // now sort the list.... |
||
370 | qSort(mTrees.begin(), mTrees.end(), treePairValue); |
||
371 | } |
||
372 | |||
373 | double FMTreeList::percentile(int pct) |
||
374 | { |
||
375 | if (mTrees.count()==0) |
||
376 | return -1.; |
||
377 | int idx = int( (pct/100.) * mTrees.count()); |
||
378 | if (idx>=0 && idx<mTrees.count()) |
||
379 | return mTrees.at(idx).second; |
||
380 | else |
||
381 | return -1; |
||
382 | } |
||
383 | |||
384 | /// random shuffle of all trees in the list |
||
385 | void FMTreeList::randomize() |
||
386 | { |
||
387 | // fill the "value" part of the tree storage with a random value for each tree |
||
388 | for (int i=0;i<mTrees.count(); ++i) { |
||
389 | mTrees[i].second = drandom(); |
||
390 | } |
||
391 | // now sort the list.... |
||
392 | qSort(mTrees.begin(), mTrees.end(), treePairValue); |
||
393 | |||
394 | } |
||
395 | |||
396 | |||
912 | werner | 397 | void FMTreeList::prepareGrids() |
398 | { |
||
399 | QRectF box = ForestManagementEngine::instance()->standGrid()->boundingBox(mStand->id()); |
||
400 | if (mStandRect==box) |
||
401 | return; |
||
402 | mStandRect = box; |
||
403 | // the memory of the grids is only reallocated if the current box is larger then the previous... |
||
404 | mStandGrid.setup(box, cHeightSize); |
||
405 | mTreeCountGrid.setup(box, cHeightSize); |
||
951 | werner | 406 | mLocalGrid.setup(box, cPxSize); |
913 | werner | 407 | // mark areas outside of the grid... |
408 | GridRunner<int> runner(ForestManagementEngine::instance()->standGrid()->grid(), box); |
||
409 | float *p=mStandGrid.begin(); |
||
410 | while (runner.next()) { |
||
411 | if (*runner.current()!=mStand->id()) |
||
914 | werner | 412 | *p=-1.f; |
913 | werner | 413 | ++p; |
414 | } |
||
951 | werner | 415 | // copy stand limits to the grid |
416 | for (int iy=0;iy<mLocalGrid.sizeY();++iy) |
||
417 | for (int ix=0;ix<mLocalGrid.sizeX();++ix) |
||
418 | mLocalGrid.valueAtIndex(ix,iy) = mStandGrid.valueAtIndex(ix/cPxPerHeight, iy/cPxPerHeight)==-1.f ? -1.f: 0.f; |
||
912 | werner | 419 | } |
885 | werner | 420 | |
912 | werner | 421 | void FMTreeList::runGrid(void (*func)(float &, int &, const Tree *, const FMTreeList *)) |
422 | { |
||
423 | if (mStandRect.isNull()) |
||
424 | prepareGrids(); |
||
885 | werner | 425 | |
1157 | werner | 426 | // set all values to 0 (within the limits of the stand grid) |
427 | for (float *p=mStandGrid.begin(); p!=mStandGrid.end(); ++p) |
||
428 | if (*p!=-1.f) |
||
429 | *p=0.f; |
||
912 | werner | 430 | mTreeCountGrid.initialize(0); |
1070 | werner | 431 | int invalid_index = 0; |
912 | werner | 432 | for (QVector<QPair<Tree*, double> >::const_iterator it=mTrees.constBegin(); it!=mTrees.constEnd(); ++it) { |
433 | const Tree* tree = it->first; |
||
434 | QPoint p = mStandGrid.indexAt(tree->position()); |
||
1070 | werner | 435 | if (mStandGrid.isIndexValid(p)) |
436 | (*func)(mStandGrid.valueAtIndex(p), mTreeCountGrid.valueAtIndex(p), tree, this); |
||
437 | else |
||
438 | ++invalid_index; |
||
912 | werner | 439 | } |
1070 | werner | 440 | if (invalid_index) |
441 | qDebug() << "FMTreeList::runGrid: invalid index: n=" << invalid_index; |
||
885 | werner | 442 | |
912 | werner | 443 | // finalization: call again for each *cell* |
444 | for (int i=0;i<mStandGrid.count();++i) |
||
445 | (*func)(mStandGrid.valueAtIndex(i), mTreeCountGrid.valueAtIndex(i), 0, this); |
||
446 | |||
447 | } |
||
448 | |||
449 | void rungrid_heightmax(float &cell, int &n, const Tree *tree, const FMTreeList *list) |
||
450 | { |
||
451 | Q_UNUSED(n); Q_UNUSED(list); |
||
452 | if (tree) |
||
453 | cell = qMax(cell, tree->height()); |
||
454 | } |
||
455 | void rungrid_basalarea(float &cell, int &n, const Tree *tree, const FMTreeList *list) |
||
456 | { |
||
457 | Q_UNUSED(list); |
||
458 | if (tree) { |
||
459 | cell += tree->basalArea(); |
||
460 | ++n; |
||
461 | } else { |
||
462 | if (n>0) |
||
463 | cell /= float(n); |
||
464 | } |
||
465 | } |
||
466 | void rungrid_volume(float &cell, int &n, const Tree *tree, const FMTreeList *list) |
||
467 | { |
||
468 | Q_UNUSED(list); |
||
469 | if (tree) { |
||
470 | cell += tree->volume(); |
||
471 | ++n; |
||
472 | } else { |
||
473 | if (n>0) |
||
474 | cell /= float(n); |
||
475 | } |
||
476 | } |
||
477 | |||
478 | void rungrid_custom(float &cell, int &n, const Tree *tree, const FMTreeList *list) |
||
479 | { |
||
480 | if (tree) { |
||
481 | *list->mRunGridCustomCell = cell; |
||
482 | TreeWrapper tw(tree); |
||
1157 | werner | 483 | cell = static_cast<float>(list->mRunGridCustom->calculate(tw)); |
912 | werner | 484 | ++n; |
485 | } |
||
486 | } |
||
487 | void FMTreeList::prepareStandGrid(QString type, QString custom_expression) |
||
488 | { |
||
489 | if(!mStand){ |
||
490 | qCDebug(abe) << "Error: FMTreeList: no current stand defined."; |
||
491 | return; |
||
492 | } |
||
493 | |||
494 | if (type==QStringLiteral("height")) { |
||
495 | return runGrid(&rungrid_heightmax); |
||
496 | } |
||
497 | |||
498 | if (type==QStringLiteral("basalArea")) |
||
499 | return runGrid(&rungrid_basalarea); |
||
500 | |||
501 | if (type==QStringLiteral("volume")) |
||
502 | return runGrid(&rungrid_volume); |
||
503 | |||
504 | if (type==QStringLiteral("custom")) { |
||
505 | mRunGridCustom = new Expression(custom_expression); |
||
506 | mRunGridCustomCell = mRunGridCustom->addVar("cell"); |
||
507 | runGrid(&rungrid_custom); |
||
508 | delete mRunGridCustom; |
||
509 | mRunGridCustom = 0; |
||
510 | return; |
||
511 | } |
||
512 | qCDebug(abe) << "FMTreeList: invalid type for prepareStandGrid: " << type; |
||
513 | } |
||
913 | werner | 514 | |
515 | void FMTreeList::exportStandGrid(QString file_name) |
||
516 | { |
||
517 | file_name = GlobalSettings::instance()->path(file_name); |
||
518 | Helper::saveToTextFile(file_name, gridToESRIRaster(mStandGrid) ); |
||
519 | qCDebug(abe) << "saved grid to file" << file_name; |
||
520 | } |
||
932 | werner | 521 | |
522 | void FMTreeList::check_locks() |
||
523 | { |
||
933 | werner | 524 | // removed the locking code again, WR20140821 |
525 | // if (mStand && mResourceUnitsLocked) { |
||
526 | // const MapGrid *map = ForestManagementEngine::instance()->standGrid(); |
||
527 | // if (map->isValid()) { |
||
528 | // map->freeLocksForStand(mStandId); |
||
529 | // mResourceUnitsLocked = false; |
||
530 | // } |
||
531 | // } |
||
932 | werner | 532 | } |
533 | |||
884 | werner | 534 | } // namespace |