diff options
Diffstat (limited to '')
-rwxr-xr-x | OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs | 145 | ||||
-rw-r--r-- | OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs | 3 | ||||
-rwxr-xr-x | OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs | 25 |
3 files changed, 93 insertions, 80 deletions
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs b/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs index dff71af..4f225ae 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs | |||
@@ -43,8 +43,17 @@ public class BSLinkset | |||
43 | static int m_nextLinksetID = 1; | 43 | static int m_nextLinksetID = 1; |
44 | public int LinksetID { get; private set; } | 44 | public int LinksetID { get; private set; } |
45 | 45 | ||
46 | // The children under the root in this linkset | 46 | // The children under the root in this linkset. |
47 | // There are two lists of children: the current children at runtime | ||
48 | // and the children at taint-time. For instance, if you delink a | ||
49 | // child from the linkset, the child is removed from m_children | ||
50 | // but the constraint won't be removed until taint time. | ||
51 | // Two lists lets this track the 'current' children and | ||
52 | // the physical 'taint' children separately. | ||
53 | // After taint processing and before the simulation step, these | ||
54 | // two lists must be the same. | ||
47 | private List<BSPhysObject> m_children; | 55 | private List<BSPhysObject> m_children; |
56 | private List<BSPhysObject> m_taintChildren; | ||
48 | 57 | ||
49 | // We lock the diddling of linkset classes to prevent any badness. | 58 | // We lock the diddling of linkset classes to prevent any badness. |
50 | // This locks the modification of the instances of this class. Changes | 59 | // This locks the modification of the instances of this class. Changes |
@@ -82,6 +91,7 @@ public class BSLinkset | |||
82 | PhysicsScene = scene; | 91 | PhysicsScene = scene; |
83 | LinksetRoot = parent; | 92 | LinksetRoot = parent; |
84 | m_children = new List<BSPhysObject>(); | 93 | m_children = new List<BSPhysObject>(); |
94 | m_taintChildren = new List<BSPhysObject>(); | ||
85 | m_mass = parent.MassRaw; | 95 | m_mass = parent.MassRaw; |
86 | } | 96 | } |
87 | 97 | ||
@@ -194,30 +204,40 @@ public class BSLinkset | |||
194 | // Routine used when rebuilding the body of the root of the linkset | 204 | // Routine used when rebuilding the body of the root of the linkset |
195 | // Destroy all the constraints have have been made to root. | 205 | // Destroy all the constraints have have been made to root. |
196 | // This is called when the root body is changing. | 206 | // This is called when the root body is changing. |
207 | // Returns 'true' of something eas actually removed and would need restoring | ||
197 | // Called at taint-time!! | 208 | // Called at taint-time!! |
198 | public void RemoveBodyDependencies(BSPrim child) | 209 | public bool RemoveBodyDependencies(BSPrim child) |
199 | { | 210 | { |
211 | bool ret = false; | ||
212 | |||
200 | lock (m_linksetActivityLock) | 213 | lock (m_linksetActivityLock) |
201 | { | 214 | { |
202 | if (IsRoot(child)) | 215 | if (IsRoot(child)) |
203 | { | 216 | { |
204 | // If the one with the dependency is root, must undo all children | 217 | // If the one with the dependency is root, must undo all children |
205 | DetailLog("{0},BSLinkset.RemoveBodyDependencies,removeChildrenForRoot,rID={1},numChild={2}", | 218 | DetailLog("{0},BSLinkset.RemoveBodyDependencies,removeChildrenForRoot,rID={1},numChild={2}", |
206 | LinksetRoot.LocalID, m_children.Count); | 219 | child.LocalID, LinksetRoot.LocalID, m_taintChildren.Count); |
207 | foreach (BSPhysObject bpo in m_children) | 220 | foreach (BSPhysObject bpo in m_taintChildren) |
208 | { | 221 | { |
209 | PhysicallyUnlinkAChildFromRoot(LinksetRoot, LinksetRoot.BSBody, bpo, bpo.BSBody); | 222 | PhysicallyUnlinkAChildFromRoot(LinksetRoot, LinksetRoot.BSBody, bpo, bpo.BSBody); |
223 | ret = true; | ||
210 | } | 224 | } |
211 | } | 225 | } |
212 | else | 226 | else |
213 | { | 227 | { |
214 | DetailLog("{0},BSLinkset.RemoveBodyDependencies,removeSingleChild,rID={1},rBody={2},cID={3},cBody={4}", | 228 | DetailLog("{0},BSLinkset.RemoveBodyDependencies,removeSingleChild,rID={1},rBody={2},cID={3},cBody={4}", |
229 | child.LocalID, | ||
215 | LinksetRoot.LocalID, LinksetRoot.BSBody.ptr.ToString("X"), | 230 | LinksetRoot.LocalID, LinksetRoot.BSBody.ptr.ToString("X"), |
216 | child.LocalID, child.BSBody.ptr.ToString("X")); | 231 | child.LocalID, child.BSBody.ptr.ToString("X")); |
217 | // Remove the dependency on the body of this one | 232 | // Remove the dependency on the body of this one |
218 | PhysicallyUnlinkAChildFromRoot(LinksetRoot, LinksetRoot.BSBody, child, child.BSBody); | 233 | if (m_taintChildren.Contains(child)) |
234 | { | ||
235 | PhysicallyUnlinkAChildFromRoot(LinksetRoot, LinksetRoot.BSBody, child, child.BSBody); | ||
236 | ret = true; | ||
237 | } | ||
219 | } | 238 | } |
220 | } | 239 | } |
240 | return ret; | ||
221 | } | 241 | } |
222 | 242 | ||
223 | // Routine used when rebuilding the body of the root of the linkset | 243 | // Routine used when rebuilding the body of the root of the linkset |
@@ -231,8 +251,8 @@ public class BSLinkset | |||
231 | if (IsRoot(child)) | 251 | if (IsRoot(child)) |
232 | { | 252 | { |
233 | DetailLog("{0},BSLinkset.RestoreBodyDependencies,restoreChildrenForRoot,rID={1},numChild={2}", | 253 | DetailLog("{0},BSLinkset.RestoreBodyDependencies,restoreChildrenForRoot,rID={1},numChild={2}", |
234 | LinksetRoot.LocalID, m_children.Count); | 254 | child.LocalID, LinksetRoot.LocalID, m_taintChildren.Count); |
235 | foreach (BSPhysObject bpo in m_children) | 255 | foreach (BSPhysObject bpo in m_taintChildren) |
236 | { | 256 | { |
237 | PhysicallyLinkAChildToRoot(LinksetRoot, LinksetRoot.BSBody, bpo, bpo.BSBody); | 257 | PhysicallyLinkAChildToRoot(LinksetRoot, LinksetRoot.BSBody, bpo, bpo.BSBody); |
238 | } | 258 | } |
@@ -240,6 +260,7 @@ public class BSLinkset | |||
240 | else | 260 | else |
241 | { | 261 | { |
242 | DetailLog("{0},BSLinkset.RestoreBodyDependencies,restoreSingleChild,rID={1},rBody={2},cID={3},cBody={4}", | 262 | DetailLog("{0},BSLinkset.RestoreBodyDependencies,restoreSingleChild,rID={1},rBody={2},cID={3},cBody={4}", |
263 | LinksetRoot.LocalID, | ||
243 | LinksetRoot.LocalID, LinksetRoot.BSBody.ptr.ToString("X"), | 264 | LinksetRoot.LocalID, LinksetRoot.BSBody.ptr.ToString("X"), |
244 | child.LocalID, child.BSBody.ptr.ToString("X")); | 265 | child.LocalID, child.BSBody.ptr.ToString("X")); |
245 | PhysicallyLinkAChildToRoot(LinksetRoot, LinksetRoot.BSBody, child, child.BSBody); | 266 | PhysicallyLinkAChildToRoot(LinksetRoot, LinksetRoot.BSBody, child, child.BSBody); |
@@ -256,7 +277,7 @@ public class BSLinkset | |||
256 | lock (m_linksetActivityLock) | 277 | lock (m_linksetActivityLock) |
257 | { | 278 | { |
258 | mass = LinksetRoot.MassRaw; | 279 | mass = LinksetRoot.MassRaw; |
259 | foreach (BSPhysObject bp in m_children) | 280 | foreach (BSPhysObject bp in m_taintChildren) |
260 | { | 281 | { |
261 | mass += bp.MassRaw; | 282 | mass += bp.MassRaw; |
262 | } | 283 | } |
@@ -272,7 +293,7 @@ public class BSLinkset | |||
272 | com = LinksetRoot.Position * LinksetRoot.MassRaw; | 293 | com = LinksetRoot.Position * LinksetRoot.MassRaw; |
273 | float totalMass = LinksetRoot.MassRaw; | 294 | float totalMass = LinksetRoot.MassRaw; |
274 | 295 | ||
275 | foreach (BSPhysObject bp in m_children) | 296 | foreach (BSPhysObject bp in m_taintChildren) |
276 | { | 297 | { |
277 | com += bp.Position * bp.MassRaw; | 298 | com += bp.Position * bp.MassRaw; |
278 | totalMass += bp.MassRaw; | 299 | totalMass += bp.MassRaw; |
@@ -291,70 +312,16 @@ public class BSLinkset | |||
291 | { | 312 | { |
292 | com = LinksetRoot.Position; | 313 | com = LinksetRoot.Position; |
293 | 314 | ||
294 | foreach (BSPhysObject bp in m_children) | 315 | foreach (BSPhysObject bp in m_taintChildren) |
295 | { | 316 | { |
296 | com += bp.Position * bp.MassRaw; | 317 | com += bp.Position * bp.MassRaw; |
297 | } | 318 | } |
298 | com /= (m_children.Count + 1); | 319 | com /= (m_taintChildren.Count + 1); |
299 | } | 320 | } |
300 | 321 | ||
301 | return com; | 322 | return com; |
302 | } | 323 | } |
303 | 324 | ||
304 | // Call each of the constraints that make up this linkset and recompute the | ||
305 | // various transforms and variables. Used when objects are added or removed | ||
306 | // from a linkset to make sure the constraints know about the new mass and | ||
307 | // geometry. | ||
308 | // Must only be called at taint time!! | ||
309 | private void RecomputeLinksetConstraintVariables() | ||
310 | { | ||
311 | float linksetMass = LinksetMass; | ||
312 | lock (m_linksetActivityLock) | ||
313 | { | ||
314 | bool somethingMissing = false; | ||
315 | foreach (BSPhysObject child in m_children) | ||
316 | { | ||
317 | BSConstraint constrain; | ||
318 | if (PhysicsScene.Constraints.TryGetConstraint(LinksetRoot.BSBody, child.BSBody, out constrain)) | ||
319 | { | ||
320 | // DetailLog("{0},BSLinkset.RecomputeLinksetConstraintVariables,taint,child={1},mass={2},A={3},B={4}", | ||
321 | // LinksetRoot.LocalID, child.LocalID, linksetMass, constrain.Body1.ID, constrain.Body2.ID); | ||
322 | constrain.RecomputeConstraintVariables(linksetMass); | ||
323 | } | ||
324 | else | ||
325 | { | ||
326 | // Non-fatal error that happens when children are being added to the linkset but | ||
327 | // their constraints have not been created yet. | ||
328 | // Caused by the fact that m_children is built at run time but building constraints | ||
329 | // happens at taint time. | ||
330 | somethingMissing = true; | ||
331 | break; | ||
332 | } | ||
333 | } | ||
334 | |||
335 | // If the whole linkset is not here, doesn't make sense to recompute linkset wide values | ||
336 | if (!somethingMissing) | ||
337 | { | ||
338 | // If this is a multiple object linkset, set everybody's center of mass to the set's center of mass | ||
339 | OMV.Vector3 centerOfMass = ComputeLinksetCenterOfMass(); | ||
340 | BulletSimAPI.SetCenterOfMassByPosRot2(LinksetRoot.BSBody.ptr, centerOfMass, OMV.Quaternion.Identity); | ||
341 | foreach (BSPhysObject child in m_children) | ||
342 | { | ||
343 | BulletSimAPI.SetCenterOfMassByPosRot2(child.BSBody.ptr, centerOfMass, OMV.Quaternion.Identity); | ||
344 | } | ||
345 | /* | ||
346 | // The root prim takes on the weight of the whole linkset | ||
347 | OMV.Vector3 inertia = BulletSimAPI.CalculateLocalInertia2(LinksetRoot.BSShape.Ptr, linksetMass); | ||
348 | BulletSimAPI.SetMassProps2(LinksetRoot.BSBody.Ptr, linksetMass, inertia); | ||
349 | OMV.Vector3 centerOfMass = ComputeLinksetCenterOfMass(); | ||
350 | BulletSimAPI.SetCenterOfMassByPosRot2(LinksetRoot.BSBody.Ptr, centerOfMass, OMV.Quaternion.Identity); | ||
351 | BulletSimAPI.UpdateInertiaTensor2(LinksetRoot.BSBody.Ptr); | ||
352 | */ | ||
353 | } | ||
354 | } | ||
355 | return; | ||
356 | } | ||
357 | |||
358 | // I am the root of a linkset and a new child is being added | 325 | // I am the root of a linkset and a new child is being added |
359 | // Called while LinkActivity is locked. | 326 | // Called while LinkActivity is locked. |
360 | private void AddChildToLinkset(BSPhysObject child) | 327 | private void AddChildToLinkset(BSPhysObject child) |
@@ -377,6 +344,7 @@ public class BSLinkset | |||
377 | { | 344 | { |
378 | DetailLog("{0},AddChildToLinkset,taint,child={1}", LinksetRoot.LocalID, child.LocalID); | 345 | DetailLog("{0},AddChildToLinkset,taint,child={1}", LinksetRoot.LocalID, child.LocalID); |
379 | // build the physical binding between me and the child | 346 | // build the physical binding between me and the child |
347 | m_taintChildren.Add(childx); | ||
380 | PhysicallyLinkAChildToRoot(rootx, rootBodyx, childx, childBodyx); | 348 | PhysicallyLinkAChildToRoot(rootx, rootBodyx, childx, childBodyx); |
381 | }); | 349 | }); |
382 | } | 350 | } |
@@ -405,13 +373,16 @@ public class BSLinkset | |||
405 | BSPhysObject childx = child; | 373 | BSPhysObject childx = child; |
406 | BulletBody childBodyx = child.BSBody; | 374 | BulletBody childBodyx = child.BSBody; |
407 | 375 | ||
408 | DetailLog("{0},RemoveChildFromLinkset,call,child={1}", | 376 | DetailLog("{0},RemoveChildFromLinkset,call,rID={1},rBody={2},cID={3},cBody={4}", |
409 | rootx.LocalID, | 377 | childx.LocalID, |
410 | rootx.LocalID, rootBodyx.ptr.ToString("X"), | 378 | rootx.LocalID, rootBodyx.ptr.ToString("X"), |
411 | childx.LocalID, childBodyx.ptr.ToString("X")); | 379 | childx.LocalID, childBodyx.ptr.ToString("X")); |
412 | 380 | ||
413 | PhysicsScene.TaintedObject("RemoveChildFromLinkset", delegate() | 381 | PhysicsScene.TaintedObject("RemoveChildFromLinkset", delegate() |
414 | { | 382 | { |
383 | if (m_taintChildren.Contains(childx)) | ||
384 | m_taintChildren.Remove(childx); | ||
385 | |||
415 | PhysicallyUnlinkAChildFromRoot(rootx, rootBodyx, childx, childBodyx); | 386 | PhysicallyUnlinkAChildFromRoot(rootx, rootBodyx, childx, childBodyx); |
416 | RecomputeLinksetConstraintVariables(); | 387 | RecomputeLinksetConstraintVariables(); |
417 | }); | 388 | }); |
@@ -551,6 +522,46 @@ public class BSLinkset | |||
551 | } | 522 | } |
552 | */ | 523 | */ |
553 | 524 | ||
525 | // Call each of the constraints that make up this linkset and recompute the | ||
526 | // various transforms and variables. Used when objects are added or removed | ||
527 | // from a linkset to make sure the constraints know about the new mass and | ||
528 | // geometry. | ||
529 | // Must only be called at taint time!! | ||
530 | private void RecomputeLinksetConstraintVariables() | ||
531 | { | ||
532 | float linksetMass = LinksetMass; | ||
533 | foreach (BSPhysObject child in m_taintChildren) | ||
534 | { | ||
535 | BSConstraint constrain; | ||
536 | if (PhysicsScene.Constraints.TryGetConstraint(LinksetRoot.BSBody, child.BSBody, out constrain)) | ||
537 | { | ||
538 | // DetailLog("{0},BSLinkset.RecomputeLinksetConstraintVariables,taint,child={1},mass={2},A={3},B={4}", | ||
539 | // LinksetRoot.LocalID, child.LocalID, linksetMass, constrain.Body1.ID, constrain.Body2.ID); | ||
540 | constrain.RecomputeConstraintVariables(linksetMass); | ||
541 | } | ||
542 | else | ||
543 | { | ||
544 | // Non-fatal error that happens when children are being added to the linkset but | ||
545 | // their constraints have not been created yet. | ||
546 | break; | ||
547 | } | ||
548 | } | ||
549 | |||
550 | // If the whole linkset is not here, doesn't make sense to recompute linkset wide values | ||
551 | if (m_children.Count == m_taintChildren.Count) | ||
552 | { | ||
553 | // If this is a multiple object linkset, set everybody's center of mass to the set's center of mass | ||
554 | OMV.Vector3 centerOfMass = ComputeLinksetCenterOfMass(); | ||
555 | BulletSimAPI.SetCenterOfMassByPosRot2(LinksetRoot.BSBody.ptr, centerOfMass, OMV.Quaternion.Identity); | ||
556 | foreach (BSPhysObject child in m_taintChildren) | ||
557 | { | ||
558 | BulletSimAPI.SetCenterOfMassByPosRot2(child.BSBody.ptr, centerOfMass, OMV.Quaternion.Identity); | ||
559 | } | ||
560 | } | ||
561 | return; | ||
562 | } | ||
563 | |||
564 | |||
554 | // Invoke the detailed logger and output something if it's enabled. | 565 | // Invoke the detailed logger and output something if it's enabled. |
555 | private void DetailLog(string msg, params Object[] args) | 566 | private void DetailLog(string msg, params Object[] args) |
556 | { | 567 | { |
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs b/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs index c879143..87ffe3e 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs | |||
@@ -1120,8 +1120,7 @@ public sealed class BSPrim : BSPhysObject | |||
1120 | { | 1120 | { |
1121 | // Called if the current prim body is about to be destroyed. | 1121 | // Called if the current prim body is about to be destroyed. |
1122 | // The problem is the constraints for Linksets which need to be updated for the new body. | 1122 | // The problem is the constraints for Linksets which need to be updated for the new body. |
1123 | Linkset.RemoveBodyDependencies(this); | 1123 | needToRestoreLinkset = Linkset.RemoveBodyDependencies(this); |
1124 | needToRestoreLinkset = true; | ||
1125 | }); | 1124 | }); |
1126 | 1125 | ||
1127 | if (needToRestoreLinkset) | 1126 | if (needToRestoreLinkset) |
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs b/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs index 2618971..648910a 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs | |||
@@ -144,34 +144,37 @@ public class BSShapeCollection : IDisposable | |||
144 | 144 | ||
145 | // Release the usage of a body. | 145 | // Release the usage of a body. |
146 | // Called when releasing use of a BSBody. BSShape is handled separately. | 146 | // Called when releasing use of a BSBody. BSShape is handled separately. |
147 | public void DereferenceBody(BulletBody shape, bool inTaintTime, BodyDestructionCallback bodyCallback ) | 147 | public void DereferenceBody(BulletBody body, bool inTaintTime, BodyDestructionCallback bodyCallback ) |
148 | { | 148 | { |
149 | if (shape.ptr == IntPtr.Zero) | 149 | if (body.ptr == IntPtr.Zero) |
150 | return; | 150 | return; |
151 | 151 | ||
152 | lock (m_collectionActivityLock) | 152 | lock (m_collectionActivityLock) |
153 | { | 153 | { |
154 | BodyDesc bodyDesc; | 154 | BodyDesc bodyDesc; |
155 | if (Bodies.TryGetValue(shape.ID, out bodyDesc)) | 155 | if (Bodies.TryGetValue(body.ID, out bodyDesc)) |
156 | { | 156 | { |
157 | bodyDesc.referenceCount--; | 157 | bodyDesc.referenceCount--; |
158 | bodyDesc.lastReferenced = System.DateTime.Now; | 158 | bodyDesc.lastReferenced = System.DateTime.Now; |
159 | Bodies[shape.ID] = bodyDesc; | 159 | Bodies[body.ID] = bodyDesc; |
160 | DetailLog("{0},BSShapeCollection.DereferenceBody,ref={1}", shape.ID, bodyDesc.referenceCount); | 160 | DetailLog("{0},BSShapeCollection.DereferenceBody,ref={1}", body.ID, bodyDesc.referenceCount); |
161 | 161 | ||
162 | // If body is no longer being used, free it -- bodies are never shared. | 162 | // If body is no longer being used, free it -- bodies are never shared. |
163 | if (bodyDesc.referenceCount == 0) | 163 | if (bodyDesc.referenceCount == 0) |
164 | { | 164 | { |
165 | Bodies.Remove(shape.ID); | 165 | Bodies.Remove(body.ID); |
166 | BSScene.TaintCallback removeOperation = delegate() | 166 | BSScene.TaintCallback removeOperation = delegate() |
167 | { | 167 | { |
168 | DetailLog("{0},BSShapeCollection.DereferenceBody,DestroyingBody. ptr={1}", | 168 | DetailLog("{0},BSShapeCollection.DereferenceBody,DestroyingBody. ptr={1}", |
169 | shape.ID, shape.ptr.ToString("X")); | 169 | body.ID, body.ptr.ToString("X")); |
170 | // If the caller needs to know, pass the event up. | ||
171 | if (bodyCallback != null) bodyCallback(body); | ||
172 | |||
170 | // Zero any reference to the shape so it is not freed when the body is deleted. | 173 | // Zero any reference to the shape so it is not freed when the body is deleted. |
171 | BulletSimAPI.SetCollisionShape2(PhysicsScene.World.ptr, shape.ptr, IntPtr.Zero); | 174 | BulletSimAPI.SetCollisionShape2(PhysicsScene.World.ptr, body.ptr, IntPtr.Zero); |
172 | // It may have already been removed from the world in which case the next is a NOOP. | 175 | // It may have already been removed from the world in which case the next is a NOOP. |
173 | BulletSimAPI.RemoveObjectFromWorld2(PhysicsScene.World.ptr, shape.ptr); | 176 | BulletSimAPI.RemoveObjectFromWorld2(PhysicsScene.World.ptr, body.ptr); |
174 | BulletSimAPI.DestroyObject2(PhysicsScene.World.ptr, shape.ptr); | 177 | BulletSimAPI.DestroyObject2(PhysicsScene.World.ptr, body.ptr); |
175 | }; | 178 | }; |
176 | // If already in taint-time, do the operations now. Otherwise queue for later. | 179 | // If already in taint-time, do the operations now. Otherwise queue for later. |
177 | if (inTaintTime) | 180 | if (inTaintTime) |
@@ -182,7 +185,7 @@ public class BSShapeCollection : IDisposable | |||
182 | } | 185 | } |
183 | else | 186 | else |
184 | { | 187 | { |
185 | DetailLog("{0},BSShapeCollection.DereferenceBody,DID NOT FIND BODY", shape.ID, bodyDesc.referenceCount); | 188 | DetailLog("{0},BSShapeCollection.DereferenceBody,DID NOT FIND BODY", body.ID, bodyDesc.referenceCount); |
186 | } | 189 | } |
187 | } | 190 | } |
188 | } | 191 | } |