diff options
author | Robert Adams | 2012-10-29 14:33:31 -0700 |
---|---|---|
committer | Robert Adams | 2012-11-03 21:14:41 -0700 |
commit | 93fe384cce42e91337f446fd658ef29ca3d9f733 (patch) | |
tree | 8ed885f7b33d929a73a8742063b5a7f9e32a98bb | |
parent | BulletSim: Add gravity force to vehicle. Some debugging additions. (diff) | |
download | opensim-SC-93fe384cce42e91337f446fd658ef29ca3d9f733.zip opensim-SC-93fe384cce42e91337f446fd658ef29ca3d9f733.tar.gz opensim-SC-93fe384cce42e91337f446fd658ef29ca3d9f733.tar.bz2 opensim-SC-93fe384cce42e91337f446fd658ef29ca3d9f733.tar.xz |
BulletSim: Use the PostTaints operation to build the linkset once before the next simulation step. This eliminates the management of children vs taintChildren and simplifies the constratin creation code.
Diffstat (limited to '')
-rwxr-xr-x | OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs | 52 | ||||
-rwxr-xr-x | OpenSim/Region/Physics/BulletSPlugin/BSLinksetConstraints.cs | 140 |
2 files changed, 68 insertions, 124 deletions
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs b/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs index 569d2e7..187951e 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs | |||
@@ -61,16 +61,7 @@ public abstract class BSLinkset | |||
61 | public int LinksetID { get; private set; } | 61 | public int LinksetID { get; private set; } |
62 | 62 | ||
63 | // The children under the root in this linkset. | 63 | // The children under the root in this linkset. |
64 | // There are two lists of children: the current children at runtime | ||
65 | // and the children at taint-time. For instance, if you delink a | ||
66 | // child from the linkset, the child is removed from m_children | ||
67 | // but the constraint won't be removed until taint time. | ||
68 | // Two lists lets this track the 'current' children and | ||
69 | // the physical 'taint' children separately. | ||
70 | // After taint processing and before the simulation step, these | ||
71 | // two lists must be the same. | ||
72 | protected HashSet<BSPhysObject> m_children; | 64 | protected HashSet<BSPhysObject> m_children; |
73 | protected HashSet<BSPhysObject> m_taintChildren; | ||
74 | 65 | ||
75 | // We lock the diddling of linkset classes to prevent any badness. | 66 | // We lock the diddling of linkset classes to prevent any badness. |
76 | // This locks the modification of the instances of this class. Changes | 67 | // This locks the modification of the instances of this class. Changes |
@@ -110,7 +101,6 @@ public abstract class BSLinkset | |||
110 | PhysicsScene = scene; | 101 | PhysicsScene = scene; |
111 | LinksetRoot = parent; | 102 | LinksetRoot = parent; |
112 | m_children = new HashSet<BSPhysObject>(); | 103 | m_children = new HashSet<BSPhysObject>(); |
113 | m_taintChildren = new HashSet<BSPhysObject>(); | ||
114 | m_mass = parent.MassRaw; | 104 | m_mass = parent.MassRaw; |
115 | } | 105 | } |
116 | 106 | ||
@@ -192,7 +182,7 @@ public abstract class BSLinkset | |||
192 | lock (m_linksetActivityLock) | 182 | lock (m_linksetActivityLock) |
193 | { | 183 | { |
194 | action(LinksetRoot); | 184 | action(LinksetRoot); |
195 | foreach (BSPhysObject po in m_taintChildren) | 185 | foreach (BSPhysObject po in m_children) |
196 | { | 186 | { |
197 | if (action(po)) | 187 | if (action(po)) |
198 | break; | 188 | break; |
@@ -201,9 +191,24 @@ public abstract class BSLinkset | |||
201 | return ret; | 191 | return ret; |
202 | } | 192 | } |
203 | 193 | ||
194 | // I am the root of a linkset and a new child is being added | ||
195 | // Called while LinkActivity is locked. | ||
196 | protected abstract void AddChildToLinkset(BSPhysObject child); | ||
197 | |||
198 | // Forcefully removing a child from a linkset. | ||
199 | // This is not being called by the child so we have to make sure the child doesn't think | ||
200 | // it's still connected to the linkset. | ||
201 | // Normal OpenSimulator operation will never do this because other SceneObjectPart information | ||
202 | // also has to be updated (like pointer to prim's parent). | ||
203 | protected abstract void RemoveChildFromOtherLinkset(BSPhysObject pchild); | ||
204 | |||
205 | // I am the root of a linkset and one of my children is being removed. | ||
206 | // Safe to call even if the child is not really in my linkset. | ||
207 | protected abstract void RemoveChildFromLinkset(BSPhysObject child); | ||
208 | |||
204 | // When physical properties are changed the linkset needs to recalculate | 209 | // When physical properties are changed the linkset needs to recalculate |
205 | // its internal properties. | 210 | // its internal properties. |
206 | // May be called at runtime or taint-time (just pass the appropriate flag). | 211 | // May be called at runtime or taint-time. |
207 | public abstract void Refresh(BSPhysObject requestor); | 212 | public abstract void Refresh(BSPhysObject requestor); |
208 | 213 | ||
209 | // The object is going dynamic (physical). Do any setup necessary | 214 | // The object is going dynamic (physical). Do any setup necessary |
@@ -238,8 +243,6 @@ public abstract class BSLinkset | |||
238 | public abstract void RestoreBodyDependencies(BSPrim child); | 243 | public abstract void RestoreBodyDependencies(BSPrim child); |
239 | 244 | ||
240 | // ================================================================ | 245 | // ================================================================ |
241 | // Below this point is internal magic | ||
242 | |||
243 | protected virtual float ComputeLinksetMass() | 246 | protected virtual float ComputeLinksetMass() |
244 | { | 247 | { |
245 | float mass = LinksetRoot.MassRaw; | 248 | float mass = LinksetRoot.MassRaw; |
@@ -264,7 +267,7 @@ public abstract class BSLinkset | |||
264 | com = LinksetRoot.Position * LinksetRoot.MassRaw; | 267 | com = LinksetRoot.Position * LinksetRoot.MassRaw; |
265 | float totalMass = LinksetRoot.MassRaw; | 268 | float totalMass = LinksetRoot.MassRaw; |
266 | 269 | ||
267 | foreach (BSPhysObject bp in m_taintChildren) | 270 | foreach (BSPhysObject bp in m_children) |
268 | { | 271 | { |
269 | com += bp.Position * bp.MassRaw; | 272 | com += bp.Position * bp.MassRaw; |
270 | totalMass += bp.MassRaw; | 273 | totalMass += bp.MassRaw; |
@@ -283,31 +286,16 @@ public abstract class BSLinkset | |||
283 | { | 286 | { |
284 | com = LinksetRoot.Position; | 287 | com = LinksetRoot.Position; |
285 | 288 | ||
286 | foreach (BSPhysObject bp in m_taintChildren) | 289 | foreach (BSPhysObject bp in m_children) |
287 | { | 290 | { |
288 | com += bp.Position * bp.MassRaw; | 291 | com += bp.Position * bp.MassRaw; |
289 | } | 292 | } |
290 | com /= (m_taintChildren.Count + 1); | 293 | com /= (m_children.Count + 1); |
291 | } | 294 | } |
292 | 295 | ||
293 | return com; | 296 | return com; |
294 | } | 297 | } |
295 | 298 | ||
296 | // I am the root of a linkset and a new child is being added | ||
297 | // Called while LinkActivity is locked. | ||
298 | protected abstract void AddChildToLinkset(BSPhysObject child); | ||
299 | |||
300 | // Forcefully removing a child from a linkset. | ||
301 | // This is not being called by the child so we have to make sure the child doesn't think | ||
302 | // it's still connected to the linkset. | ||
303 | // Normal OpenSimulator operation will never do this because other SceneObjectPart information | ||
304 | // also has to be updated (like pointer to prim's parent). | ||
305 | protected abstract void RemoveChildFromOtherLinkset(BSPhysObject pchild); | ||
306 | |||
307 | // I am the root of a linkset and one of my children is being removed. | ||
308 | // Safe to call even if the child is not really in my linkset. | ||
309 | protected abstract void RemoveChildFromLinkset(BSPhysObject child); | ||
310 | |||
311 | // Invoke the detailed logger and output something if it's enabled. | 299 | // Invoke the detailed logger and output something if it's enabled. |
312 | protected void DetailLog(string msg, params Object[] args) | 300 | protected void DetailLog(string msg, params Object[] args) |
313 | { | 301 | { |
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSLinksetConstraints.cs b/OpenSim/Region/Physics/BulletSPlugin/BSLinksetConstraints.cs index 086aa12..6c1fa2a 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSLinksetConstraints.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSLinksetConstraints.cs | |||
@@ -54,7 +54,7 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
54 | // Queue to happen after all the other taint processing | 54 | // Queue to happen after all the other taint processing |
55 | PhysicsScene.PostTaintObject("BSLinksetContraints.Refresh", requestor.LocalID, delegate() | 55 | PhysicsScene.PostTaintObject("BSLinksetContraints.Refresh", requestor.LocalID, delegate() |
56 | { | 56 | { |
57 | RecomputeLinksetConstraintVariables(); | 57 | RecomputeLinksetConstraints(); |
58 | }); | 58 | }); |
59 | 59 | ||
60 | } | 60 | } |
@@ -98,24 +98,13 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
98 | 98 | ||
99 | lock (m_linksetActivityLock) | 99 | lock (m_linksetActivityLock) |
100 | { | 100 | { |
101 | if (IsRoot(child)) | 101 | // Just undo all the constraints for this linkset. Rebuild at the end of the step. |
102 | { | 102 | DetailLog("{0},BSLinksetConstraint.RemoveBodyDependencies,removeChildrenForRoot,rID={1},rBody={2}", |
103 | // If the one with the dependency is root, must undo all children | 103 | child.LocalID, LinksetRoot.LocalID, LinksetRoot.BSBody.ptr.ToString("X")); |
104 | DetailLog("{0},BSLinksetConstraint.RemoveBodyDependencies,removeChildrenForRoot,rID={1},rBody={2}", | ||
105 | child.LocalID, LinksetRoot.LocalID, LinksetRoot.BSBody.ptr.ToString("X")); | ||
106 | 104 | ||
107 | ret = PhysicallyUnlinkAllChildrenFromRoot(LinksetRoot); | 105 | ret = PhysicallyUnlinkAllChildrenFromRoot(LinksetRoot); |
108 | } | 106 | // Cause the constraints, et al to be rebuilt before the next simulation step. |
109 | else | 107 | Refresh(LinksetRoot); |
110 | { | ||
111 | DetailLog("{0},BSLinksetConstraint.RemoveBodyDependencies,removeSingleChild,rID={1},rBody={2},cID={3},cBody={4}", | ||
112 | child.LocalID, | ||
113 | LinksetRoot.LocalID, LinksetRoot.BSBody.ptr.ToString("X"), | ||
114 | child.LocalID, child.BSBody.ptr.ToString("X")); | ||
115 | // ret = PhysicallyUnlinkAChildFromRoot(LinksetRoot, child); | ||
116 | // Despite the function name, this removes any link to the specified object. | ||
117 | ret = PhysicallyUnlinkAllChildrenFromRoot(child); | ||
118 | } | ||
119 | } | 108 | } |
120 | return ret; | 109 | return ret; |
121 | } | 110 | } |
@@ -125,26 +114,7 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
125 | // Called at taint-time!! | 114 | // Called at taint-time!! |
126 | public override void RestoreBodyDependencies(BSPrim child) | 115 | public override void RestoreBodyDependencies(BSPrim child) |
127 | { | 116 | { |
128 | lock (m_linksetActivityLock) | 117 | // The Refresh operation will build any missing constraints. |
129 | { | ||
130 | if (IsRoot(child)) | ||
131 | { | ||
132 | DetailLog("{0},BSLinksetConstraint.RestoreBodyDependencies,restoreChildrenForRoot,rID={1},numChild={2}", | ||
133 | child.LocalID, LinksetRoot.LocalID, m_taintChildren.Count); | ||
134 | foreach (BSPhysObject bpo in m_taintChildren) | ||
135 | { | ||
136 | PhysicallyLinkAChildToRoot(LinksetRoot, bpo); | ||
137 | } | ||
138 | } | ||
139 | else | ||
140 | { | ||
141 | DetailLog("{0},BSLinksetConstraint.RestoreBodyDependencies,restoreSingleChild,rID={1},rBody={2},cID={3},cBody={4}", | ||
142 | LinksetRoot.LocalID, | ||
143 | LinksetRoot.LocalID, LinksetRoot.BSBody.ptr.ToString("X"), | ||
144 | child.LocalID, child.BSBody.ptr.ToString("X")); | ||
145 | PhysicallyLinkAChildToRoot(LinksetRoot, child); | ||
146 | } | ||
147 | } | ||
148 | } | 118 | } |
149 | 119 | ||
150 | // ================================================================ | 120 | // ================================================================ |
@@ -163,18 +133,7 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
163 | 133 | ||
164 | DetailLog("{0},AddChildToLinkset,call,child={1}", LinksetRoot.LocalID, child.LocalID); | 134 | DetailLog("{0},AddChildToLinkset,call,child={1}", LinksetRoot.LocalID, child.LocalID); |
165 | 135 | ||
166 | PhysicsScene.TaintedObject("AddChildToLinkset", delegate() | 136 | // Cause constraints and assorted properties to be recomputed before the next simulation step. |
167 | { | ||
168 | DetailLog("{0},AddChildToLinkset,taint,rID={1},rBody={2},cID={3},cBody={4}", | ||
169 | rootx.LocalID, | ||
170 | rootx.LocalID, rootx.BSBody.ptr.ToString("X"), | ||
171 | childx.LocalID, childx.BSBody.ptr.ToString("X")); | ||
172 | // Since this is taint-time, the body and shape could have changed for the child | ||
173 | rootx.ForcePosition = rootx.Position; // DEBUG | ||
174 | childx.ForcePosition = childx.Position; // DEBUG | ||
175 | PhysicallyLinkAChildToRoot(rootx, childx); | ||
176 | m_taintChildren.Add(child); | ||
177 | }); | ||
178 | Refresh(LinksetRoot); | 137 | Refresh(LinksetRoot); |
179 | } | 138 | } |
180 | return; | 139 | return; |
@@ -207,7 +166,6 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
207 | 166 | ||
208 | PhysicsScene.TaintedObject("RemoveChildFromLinkset", delegate() | 167 | PhysicsScene.TaintedObject("RemoveChildFromLinkset", delegate() |
209 | { | 168 | { |
210 | m_taintChildren.Remove(child); | ||
211 | PhysicallyUnlinkAChildFromRoot(rootx, childx); | 169 | PhysicallyUnlinkAChildFromRoot(rootx, childx); |
212 | }); | 170 | }); |
213 | // See that the linkset parameters are recomputed at the end of the taint time. | 171 | // See that the linkset parameters are recomputed at the end of the taint time. |
@@ -225,6 +183,12 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
225 | // Called at taint time! | 183 | // Called at taint time! |
226 | private void PhysicallyLinkAChildToRoot(BSPhysObject rootPrim, BSPhysObject childPrim) | 184 | private void PhysicallyLinkAChildToRoot(BSPhysObject rootPrim, BSPhysObject childPrim) |
227 | { | 185 | { |
186 | // Don't build the constraint when asked. Put it off until just before the simulation step. | ||
187 | Refresh(rootPrim); | ||
188 | } | ||
189 | |||
190 | private BSConstraint BuildConstraint(BSPhysObject rootPrim, BSPhysObject childPrim) | ||
191 | { | ||
228 | // Zero motion for children so they don't interpolate | 192 | // Zero motion for children so they don't interpolate |
229 | childPrim.ZeroMotion(); | 193 | childPrim.ZeroMotion(); |
230 | 194 | ||
@@ -235,7 +199,7 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
235 | // real world coordinate of midpoint between the two objects | 199 | // real world coordinate of midpoint between the two objects |
236 | OMV.Vector3 midPoint = rootPrim.Position + (childRelativePosition / 2); | 200 | OMV.Vector3 midPoint = rootPrim.Position + (childRelativePosition / 2); |
237 | 201 | ||
238 | DetailLog("{0},BSLinksetConstraint.PhysicallyLinkAChildToRoot,taint,root={1},rBody={2},child={3},cBody={4},rLoc={5},cLoc={6},midLoc={7}", | 202 | DetailLog("{0},BSLinksetConstraint.BuildConstraint,taint,root={1},rBody={2},child={3},cBody={4},rLoc={5},cLoc={6},midLoc={7}", |
239 | rootPrim.LocalID, | 203 | rootPrim.LocalID, |
240 | rootPrim.LocalID, rootPrim.BSBody.ptr.ToString("X"), | 204 | rootPrim.LocalID, rootPrim.BSBody.ptr.ToString("X"), |
241 | childPrim.LocalID, childPrim.BSBody.ptr.ToString("X"), | 205 | childPrim.LocalID, childPrim.BSBody.ptr.ToString("X"), |
@@ -297,6 +261,7 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
297 | { | 261 | { |
298 | constrain.SetSolverIterations(PhysicsScene.Params.linkConstraintSolverIterations); | 262 | constrain.SetSolverIterations(PhysicsScene.Params.linkConstraintSolverIterations); |
299 | } | 263 | } |
264 | return constrain; | ||
300 | } | 265 | } |
301 | 266 | ||
302 | // Remove linkage between myself and a particular child | 267 | // Remove linkage between myself and a particular child |
@@ -337,56 +302,47 @@ public sealed class BSLinksetConstraints : BSLinkset | |||
337 | } | 302 | } |
338 | 303 | ||
339 | // Call each of the constraints that make up this linkset and recompute the | 304 | // Call each of the constraints that make up this linkset and recompute the |
340 | // various transforms and variables. Used when objects are added or removed | 305 | // various transforms and variables. Create constraints of not created yet. |
341 | // from a linkset to make sure the constraints know about the new mass and | 306 | // Called before the simulation step to make sure the constraint based linkset |
342 | // geometry. | 307 | // is all initialized. |
343 | // Must only be called at taint time!! | 308 | // Must only be called at taint time!! |
344 | private void RecomputeLinksetConstraintVariables() | 309 | private void RecomputeLinksetConstraints() |
345 | { | 310 | { |
346 | float linksetMass = LinksetMass; | 311 | float linksetMass = LinksetMass; |
347 | foreach (BSPhysObject child in m_taintChildren) | 312 | LinksetRoot.UpdatePhysicalMassProperties(linksetMass); |
313 | |||
314 | // For a multiple object linkset, set everybody's center of mass to the set's center of mass | ||
315 | OMV.Vector3 centerOfMass = ComputeLinksetCenterOfMass(); | ||
316 | BulletSimAPI.SetCenterOfMassByPosRot2(LinksetRoot.BSBody.ptr, centerOfMass, OMV.Quaternion.Identity); | ||
317 | |||
318 | // BulletSimAPI.SetCollisionFilterMask2(LinksetRoot.BSBody.ptr, | ||
319 | // (uint)CollisionFilterGroups.LinksetFilter, (uint)CollisionFilterGroups.LinksetMask); | ||
320 | DetailLog("{0},BSLinksetConstraint.RecomputeLinksetConstraints,setCenterOfMass,COM={1},rBody={2},linksetMass={3}", | ||
321 | LinksetRoot.LocalID, centerOfMass, LinksetRoot.BSBody.ptr.ToString("X"), linksetMass); | ||
322 | |||
323 | foreach (BSPhysObject child in m_children) | ||
348 | { | 324 | { |
325 | // A child in the linkset physically shows the mass of the whole linkset. | ||
326 | // This allows Bullet to apply enough force on the child to move the whole linkset. | ||
327 | // (Also do the mass stuff before recomputing the constraint so mass is not zero.) | ||
328 | BulletSimAPI.SetCenterOfMassByPosRot2(child.BSBody.ptr, centerOfMass, OMV.Quaternion.Identity); | ||
329 | child.UpdatePhysicalMassProperties(linksetMass); | ||
330 | |||
349 | BSConstraint constrain; | 331 | BSConstraint constrain; |
350 | if (PhysicsScene.Constraints.TryGetConstraint(LinksetRoot.BSBody, child.BSBody, out constrain)) | 332 | if (!PhysicsScene.Constraints.TryGetConstraint(LinksetRoot.BSBody, child.BSBody, out constrain)) |
351 | { | ||
352 | // DetailLog("{0},BSLinksetConstraint.RecomputeLinksetConstraintVariables,taint,child={1},mass={2},A={3},B={4}", | ||
353 | // LinksetRoot.LocalID, child.LocalID, linksetMass, constrain.Body1.ID, constrain.Body2.ID); | ||
354 | constrain.RecomputeConstraintVariables(linksetMass); | ||
355 | } | ||
356 | else | ||
357 | { | 333 | { |
358 | // Non-fatal error that happens when children are being added to the linkset but | 334 | // If constraint doesn't exist yet, create it. |
359 | // their constraints have not been created yet. | 335 | constrain = BuildConstraint(LinksetRoot, child); |
360 | break; | ||
361 | } | 336 | } |
362 | } | 337 | constrain.RecomputeConstraintVariables(linksetMass); |
363 | 338 | ||
364 | // If the whole linkset is not here, doesn't make sense to recompute linkset wide values | 339 | // DEBUG: see of inter-linkset collisions are causing problems |
365 | if (m_children.Count == m_taintChildren.Count) | 340 | // BulletSimAPI.SetCollisionFilterMask2(child.BSBody.ptr, |
366 | { | 341 | // (uint)CollisionFilterGroups.LinksetFilter, (uint)CollisionFilterGroups.LinksetMask); |
367 | // If this is a multiple object linkset, set everybody's center of mass to the set's center of mass | ||
368 | OMV.Vector3 centerOfMass = ComputeLinksetCenterOfMass(); | ||
369 | BulletSimAPI.SetCenterOfMassByPosRot2(LinksetRoot.BSBody.ptr, centerOfMass, OMV.Quaternion.Identity); | ||
370 | // BulletSimAPI.SetCollisionFilterMask2(LinksetRoot.BSBody.ptr, | ||
371 | // (uint)CollisionFilterGroups.LinksetFilter, (uint)CollisionFilterGroups.LinksetMask); | ||
372 | DetailLog("{0},BSLinksetConstraint.RecomputeLinksetConstraintVariables,setCenterOfMass,COM={1},rBody={2},linksetMass={3}", | ||
373 | LinksetRoot.LocalID, centerOfMass, LinksetRoot.BSBody.ptr.ToString("X"), linksetMass); | ||
374 | foreach (BSPhysObject child in m_taintChildren) | ||
375 | { | ||
376 | BulletSimAPI.SetCenterOfMassByPosRot2(child.BSBody.ptr, centerOfMass, OMV.Quaternion.Identity); | ||
377 | // A child in the linkset physically shows the mass of the whole linkset. | ||
378 | // This allows Bullet to apply enough force on the child to move the whole linkset. | ||
379 | child.UpdatePhysicalMassProperties(linksetMass); | ||
380 | // DEBUG: see of inter-linkset collisions are causing problems | ||
381 | // BulletSimAPI.SetCollisionFilterMask2(child.BSBody.ptr, | ||
382 | // (uint)CollisionFilterGroups.LinksetFilter, (uint)CollisionFilterGroups.LinksetMask); | ||
383 | } | ||
384 | // Also update the root's physical mass to the whole linkset | ||
385 | LinksetRoot.UpdatePhysicalMassProperties(linksetMass); | ||
386 | 342 | ||
387 | // BulletSimAPI.DumpAllInfo2(PhysicsScene.World.ptr); // DEBUG DEBUG DEBUG | 343 | // BulletSimAPI.DumpConstraint2(PhysicsScene.World.ptr, constrain.Constraint.ptr); // DEBUG DEBUG |
388 | } | 344 | } |
389 | return; | 345 | |
390 | } | 346 | } |
391 | } | 347 | } |
392 | } | 348 | } |