aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim')
-rw-r--r--OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RegionInfoCache.cs506
-rw-r--r--OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs85
2 files changed, 482 insertions, 109 deletions
diff --git a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RegionInfoCache.cs b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RegionInfoCache.cs
index be8a9a2..4dc9033 100644
--- a/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RegionInfoCache.cs
+++ b/OpenSim/Region/CoreModules/ServiceConnectorsOut/Grid/RegionInfoCache.cs
@@ -26,6 +26,7 @@
26 */ 26 */
27using System; 27using System;
28using System.Reflection; 28using System.Reflection;
29using System.Threading;
29using System.Collections.Generic; 30using System.Collections.Generic;
30using OpenSim.Framework; 31using OpenSim.Framework;
31using OpenSim.Services.Interfaces; 32using OpenSim.Services.Interfaces;
@@ -37,54 +38,17 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid
37{ 38{
38 public class RegionInfoCache 39 public class RegionInfoCache
39 { 40 {
40 private const double CACHE_EXPIRATION_SECONDS = 300.0; // 5 minutes 41 private const double CACHE_EXPIRATION_SECONDS = 120; // 2 minutes opensim regions change a lot
41 42
42// private static readonly ILog m_log = 43// private static readonly ILog m_log =
43// LogManager.GetLogger( 44// LogManager.GetLogger(
44// MethodBase.GetCurrentMethod().DeclaringType); 45// MethodBase.GetCurrentMethod().DeclaringType);
45 46
46 internal struct ScopedRegionUUID 47 private RegionsExpiringCache m_Cache;
47 {
48 public UUID m_scopeID;
49 public UUID m_regionID;
50 public ScopedRegionUUID(UUID scopeID, UUID regionID)
51 {
52 m_scopeID = scopeID;
53 m_regionID = regionID;
54 }
55 }
56
57 internal struct ScopedRegionName
58 {
59 public UUID m_scopeID;
60 public string m_name;
61 public ScopedRegionName(UUID scopeID, string name)
62 {
63 m_scopeID = scopeID;
64 m_name = name;
65 }
66 }
67
68 internal struct ScopedRegionPosition
69 {
70 public UUID m_scopeID;
71 public ulong m_regionHandle;
72 public ScopedRegionPosition(UUID scopeID, ulong handle)
73 {
74 m_scopeID = scopeID;
75 m_regionHandle = handle;
76 }
77 }
78
79 private ExpiringCache<ScopedRegionUUID, GridRegion> m_UUIDCache;
80 private ExpiringCache<ScopedRegionName, ScopedRegionUUID> m_NameCache;
81 private ExpiringCache<ScopedRegionPosition, GridRegion> m_PositionCache;
82 48
83 public RegionInfoCache() 49 public RegionInfoCache()
84 { 50 {
85 m_UUIDCache = new ExpiringCache<ScopedRegionUUID, GridRegion>(); 51 m_Cache = new RegionsExpiringCache();
86 m_NameCache = new ExpiringCache<ScopedRegionName, ScopedRegionUUID>();
87 m_PositionCache = new ExpiringCache<ScopedRegionPosition, GridRegion>();
88 } 52 }
89 53
90 public void Cache(GridRegion rinfo) 54 public void Cache(GridRegion rinfo)
@@ -101,18 +65,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid
101 if (rinfo == null) 65 if (rinfo == null)
102 return; 66 return;
103 67
104 ScopedRegionUUID id = new ScopedRegionUUID(scopeID,regionID); 68 m_Cache.AddOrUpdate(scopeID, rinfo, CACHE_EXPIRATION_SECONDS);
105
106 // Cache even null accounts
107 m_UUIDCache.AddOrUpdate(id, rinfo, CACHE_EXPIRATION_SECONDS);
108 if (rinfo != null)
109 {
110 ScopedRegionName name = new ScopedRegionName(scopeID,rinfo.RegionName);
111 m_NameCache.AddOrUpdate(name, id, CACHE_EXPIRATION_SECONDS);
112
113 ScopedRegionPosition pos = new ScopedRegionPosition(scopeID, rinfo.RegionHandle);
114 m_PositionCache.AddOrUpdate(pos, rinfo, CACHE_EXPIRATION_SECONDS);
115 }
116 } 69 }
117 70
118 public GridRegion Get(UUID scopeID, UUID regionID, out bool inCache) 71 public GridRegion Get(UUID scopeID, UUID regionID, out bool inCache)
@@ -120,8 +73,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid
120 inCache = false; 73 inCache = false;
121 74
122 GridRegion rinfo = null; 75 GridRegion rinfo = null;
123 ScopedRegionUUID id = new ScopedRegionUUID(scopeID,regionID); 76 if (m_Cache.TryGetValue(scopeID, regionID, out rinfo))
124 if (m_UUIDCache.TryGetValue(id, out rinfo))
125 { 77 {
126 inCache = true; 78 inCache = true;
127 return rinfo; 79 return rinfo;
@@ -135,8 +87,7 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid
135 inCache = false; 87 inCache = false;
136 88
137 GridRegion rinfo = null; 89 GridRegion rinfo = null;
138 ScopedRegionPosition pos = new ScopedRegionPosition(scopeID, handle); 90 if (m_Cache.TryGetValue(scopeID, handle, out rinfo))
139 if (m_PositionCache.TryGetValue(pos, out rinfo))
140 { 91 {
141 inCache = true; 92 inCache = true;
142 return rinfo; 93 return rinfo;
@@ -145,25 +96,450 @@ namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid
145 return null; 96 return null;
146 } 97 }
147 98
148
149 public GridRegion Get(UUID scopeID, string name, out bool inCache) 99 public GridRegion Get(UUID scopeID, string name, out bool inCache)
150 { 100 {
151 inCache = false; 101 inCache = false;
152 102
153 ScopedRegionName sname = new ScopedRegionName(scopeID,name); 103 GridRegion rinfo = null;
104 if (m_Cache.TryGetValue(scopeID, name, out rinfo))
105 {
106 inCache = true;
107 return rinfo;
108 }
109
110 return null;
111 }
112 }
113
114
115 // following code partialy adapted from lib OpenMetaverse
116 public class RegionKey : IComparable<RegionKey>
117 {
118 private UUID m_scopeID;
119 private UUID m_RegionID;
120 private DateTime m_expirationDate;
121
122 public RegionKey(UUID scopeID, UUID id)
123 {
124 m_scopeID = scopeID;
125 m_RegionID = id;
126 }
127
128 public UUID ScopeID
129 {
130 get { return m_scopeID; }
131 }
132 public DateTime ExpirationDate
133 {
134 get { return m_expirationDate; }
135 set { m_expirationDate = value; }
136 }
137
138 public int GetHaskCode()
139 {
140 int hash = m_scopeID.GetHashCode();
141 hash += hash * 23 + m_RegionID.GetHashCode();
142 return hash;
143 }
144
145 public int CompareTo(RegionKey other)
146 {
147 return GetHashCode().CompareTo(other.GetHashCode());
148 }
149 }
150
151 public class RegionInfoByScope
152 {
153 private Dictionary<string, RegionKey> byname;
154 private Dictionary<ulong, RegionKey> byhandle;
155
156 public RegionInfoByScope()
157 {
158 byname = new Dictionary<string, RegionKey>();
159 byhandle = new Dictionary<ulong, RegionKey>();
160 }
161
162 public RegionInfoByScope(GridRegion region, RegionKey key)
163 {
164 byname = new Dictionary<string, RegionKey>();
165 byhandle = new Dictionary<ulong, RegionKey>();
166
167 byname[region.RegionName] = key;
168 byhandle[region.RegionHandle] = key;
169 }
154 170
155 ScopedRegionUUID id; 171 public void AddRegion(GridRegion region, RegionKey key)
156 if (m_NameCache.TryGetValue(sname, out id)) 172 {
173 if(byname == null)
174 byname = new Dictionary<string, RegionKey>();
175 if(byhandle == null)
176 byhandle = new Dictionary<ulong, RegionKey>();
177
178 byname[region.RegionName] = key;
179 byhandle[region.RegionHandle] = key;
180 }
181
182 public void RemoveRegion(GridRegion region)
183 {
184 if(byname != null)
185 byname.Remove(region.RegionName);
186 if(byhandle != null)
187 byhandle.Remove(region.RegionHandle);
188 }
189
190 public void Clear()
191 {
192 if(byname != null)
193 byname.Clear();
194 if(byhandle != null)
195 byhandle.Clear();
196 byname = null;
197 byhandle = null;
198 }
199
200 public RegionKey get(string name)
201 {
202 if(byname == null || !byname.ContainsKey(name))
203 return null;
204 return byname[name];
205 }
206
207 public RegionKey get(ulong handle)
208 {
209 if(byhandle == null || !byhandle.ContainsKey(handle))
210 return null;
211 return byhandle[handle];
212 }
213
214 public int Count()
215 {
216 if(byname == null)
217 return 0;
218 else
219 return byname.Count;
220 }
221 }
222
223 public sealed class RegionsExpiringCache
224 {
225 const double CACHE_PURGE_HZ = 60;
226 const int MAX_LOCK_WAIT = 5000; // milliseconds
227
228 /// <summary>For thread safety</summary>
229 object syncRoot = new object();
230 /// <summary>For thread safety</summary>
231 object isPurging = new object();
232
233 Dictionary<RegionKey, GridRegion> timedStorage = new Dictionary<RegionKey, GridRegion>();
234 Dictionary<UUID, RegionInfoByScope> InfobyScope = new Dictionary<UUID, RegionInfoByScope>();
235 private System.Timers.Timer timer = new System.Timers.Timer(TimeSpan.FromSeconds(CACHE_PURGE_HZ).TotalMilliseconds);
236
237 public RegionsExpiringCache()
238 {
239 timer.Elapsed += PurgeCache;
240 timer.Start();
241 }
242
243 public bool Add(UUID scope, GridRegion region, double expirationSeconds)
244 {
245 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
246 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
247
248 RegionKey key = new RegionKey(scope , region.RegionID);
249
250 try
157 { 251 {
158 GridRegion rinfo = null; 252 if (timedStorage.ContainsKey(key))
159 if (m_UUIDCache.TryGetValue(id, out rinfo)) 253 return false;
254
255 key.ExpirationDate = DateTime.UtcNow + TimeSpan.FromSeconds(expirationSeconds);
256 timedStorage.Add(key, region);
257
258 RegionInfoByScope ris = null;
259 if(!InfobyScope.TryGetValue(scope, out ris) || ris == null)
160 { 260 {
161 inCache = true; 261 ris = new RegionInfoByScope(region, key);
162 return rinfo; 262 InfobyScope[scope] = ris;
163 } 263 }
264 else
265 ris.AddRegion(region, key);
266
267 return true;
164 } 268 }
269 finally { Monitor.Exit(syncRoot);}
270 }
271
272 public bool AddOrUpdate(UUID scope, GridRegion region, double expirationSeconds)
273 {
274 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
275 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
165 276
166 return null; 277 try
278 {
279 RegionKey key = new RegionKey(scope, region.RegionID);
280 key.ExpirationDate = DateTime.UtcNow + TimeSpan.FromSeconds(expirationSeconds);
281
282 if (timedStorage.ContainsKey(key))
283 {
284 timedStorage.Remove(key);
285 timedStorage.Add(key, region);
286
287 if(!InfobyScope.ContainsKey(scope))
288 {
289 RegionInfoByScope ris = new RegionInfoByScope(region, key);
290 InfobyScope[scope] = ris;
291 }
292 return false;
293 }
294 else
295 {
296 timedStorage.Add(key, region);
297 RegionInfoByScope ris = null;
298 if(!InfobyScope.TryGetValue(scope, out ris) || ris == null)
299 {
300 ris = new RegionInfoByScope(region,key);
301 InfobyScope[scope] = ris;
302 }
303 else
304 ris.AddRegion(region,key);
305 return true;
306 }
307 }
308 finally { Monitor.Exit(syncRoot); }
309 }
310
311 public void Clear()
312 {
313 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
314 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
315 try
316 {
317 timedStorage.Clear();
318 InfobyScope.Clear();
319 }
320 finally { Monitor.Exit(syncRoot); }
321 }
322
323 public bool Contains(UUID scope, GridRegion region)
324 {
325 RegionKey key = new RegionKey(scope, region.RegionID);
326 return Contains(key);
327 }
328
329 public bool Contains(RegionKey key)
330 {
331 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
332 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
333 try
334 {
335 return timedStorage.ContainsKey(key);
336 }
337 finally { Monitor.Exit(syncRoot); }
338 }
339
340 public int Count
341 {
342 get
343 {
344 return timedStorage.Count;
345 }
346 }
347
348 public bool Remove(UUID scope, GridRegion region)
349 {
350 RegionKey key = new RegionKey(scope, region.RegionID);
351
352 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
353 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
354 try
355 {
356 if (timedStorage.ContainsKey(key))
357 {
358 RegionInfoByScope ris = null;
359 if(InfobyScope.TryGetValue(scope, out ris) && ris != null)
360 {
361 GridRegion r = timedStorage[key];
362 if(r != null)
363 ris.RemoveRegion(r);
364 if(ris.Count() == 0)
365 InfobyScope.Remove(scope);
366 }
367 timedStorage.Remove(key);
368 return true;
369 }
370 else
371 return false;
372 }
373 finally { Monitor.Exit(syncRoot); }
374 }
375
376 public bool TryGetValue(RegionKey key, out GridRegion value)
377 {
378 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
379 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
380 try
381 {
382 if (timedStorage.ContainsKey(key))
383 {
384 value = timedStorage[key];
385 return true;
386 }
387 }
388 finally { Monitor.Exit(syncRoot); }
389
390 value = null;
391 return false;
392 }
393
394 public bool TryGetValue(UUID scope, UUID id, out GridRegion value)
395 {
396 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
397 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
398 try
399 {
400 RegionKey rk = new RegionKey(scope, id);
401 if(timedStorage.ContainsKey(rk))
402 {
403 value = timedStorage[rk];
404 return true;
405 }
406 }
407 finally { Monitor.Exit(syncRoot); }
408
409 value = null;
410 return false;
411 }
412
413 public bool TryGetValue(UUID scope, string name, out GridRegion value)
414 {
415 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
416 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
417 try
418 {
419 value = null;
420 RegionInfoByScope ris = null;
421 if(!InfobyScope.TryGetValue(scope, out ris) || ris == null)
422 return false;
423
424 RegionKey key = ris.get(name);
425 if(key == null)
426 return false;
427
428 if(timedStorage.ContainsKey(key))
429 {
430 value = timedStorage[key];
431 return true;
432 }
433 }
434 finally { Monitor.Exit(syncRoot); }
435
436 return false;
437 }
438
439 public bool TryGetValue(UUID scope, ulong handle, out GridRegion value)
440 {
441 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
442 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
443 try
444 {
445 value = null;
446 RegionInfoByScope ris = null;
447 if(!InfobyScope.TryGetValue(scope, out ris) || ris == null)
448 return false;
449
450 RegionKey key = ris.get(handle);
451 if(key == null)
452 return false;
453
454 if(timedStorage.ContainsKey(key))
455 {
456 value = timedStorage[key];
457 return true;
458 }
459 }
460 finally { Monitor.Exit(syncRoot); }
461
462 value = null;
463 return false;
464 }
465
466 public bool Update(UUID scope, GridRegion region, double expirationSeconds)
467 {
468 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
469 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
470
471 RegionKey key = new RegionKey(scope, region.RegionID);
472 try
473 {
474 if (!timedStorage.ContainsKey(key))
475 return false;
476
477 timedStorage.Remove(key);
478 key.ExpirationDate = DateTime.UtcNow + TimeSpan.FromSeconds(expirationSeconds);
479 timedStorage.Add(key, region);
480 return true;
481 }
482 finally { Monitor.Exit(syncRoot); }
483 }
484
485 /// <summary>
486 /// Purges expired objects from the cache. Called automatically by the purge timer.
487 /// </summary>
488 private void PurgeCache(object sender, System.Timers.ElapsedEventArgs e)
489 {
490 // Only let one thread purge at once - a buildup could cause a crash
491 // This could cause the purge to be delayed while there are lots of read/write ops
492 // happening on the cache
493 if (!Monitor.TryEnter(isPurging))
494 return;
495
496 DateTime signalTime = DateTime.UtcNow;
497
498 try
499 {
500 // If we fail to acquire a lock on the synchronization root after MAX_LOCK_WAIT, skip this purge cycle
501 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
502 return;
503 try
504 {
505 OpenMetaverse.Lazy<List<object>> expiredItems = new OpenMetaverse.Lazy<List<object>>();
506
507 foreach (RegionKey timedKey in timedStorage.Keys)
508 {
509 if (timedKey.ExpirationDate < signalTime)
510 {
511 // Mark the object for purge
512 expiredItems.Value.Add(timedKey);
513 }
514 else
515 {
516 break;
517 }
518 }
519
520
521 RegionInfoByScope ris;
522 if (expiredItems.IsValueCreated)
523 {
524 foreach (RegionKey key in expiredItems.Value)
525 {
526 ris = null;
527 if(InfobyScope.TryGetValue(key.ScopeID, out ris) && ris != null)
528 {
529 GridRegion r = timedStorage[key];
530 if(r != null)
531 ris.RemoveRegion(r);
532
533 if(ris.Count() == 0)
534 InfobyScope.Remove(key.ScopeID);
535 }
536 timedStorage.Remove(key);
537 }
538 }
539 }
540 finally { Monitor.Exit(syncRoot); }
541 }
542 finally { Monitor.Exit(isPurging); }
167 } 543 }
168 } 544 }
169} 545}
diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
index 6d2e2c8..fb35a54 100644
--- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
+++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
@@ -6394,64 +6394,61 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
6394 { 6394 {
6395 m_host.AddScriptLPS(1); 6395 m_host.AddScriptLPS(1);
6396 6396
6397 // edge will be used to pass the Region Coordinates offset 6397 if(dir.x == 0.0 && dir.y == 0.0)
6398 // we want to check for a neighboring sim 6398 return 1; // SL wiki
6399 LSL_Vector edge = new LSL_Vector(0, 0, 0); 6399
6400 float rsx = World.RegionInfo.RegionSizeX;
6401 float rsy = World.RegionInfo.RegionSizeY;
6402
6403 // can understand what sl does if position is not in region, so do something :)
6404 float px = (float)Util.Clamp(pos.x, 0.5, rsx - 0.5);
6405 float py = (float)Util.Clamp(pos.y, 0.5, rsy - 0.5);
6406
6407 float ex, ey;
6400 6408
6401 if (dir.x == 0) 6409 if (dir.x == 0)
6402 { 6410 {
6403 if (dir.y == 0) 6411 ex = px;
6404 { 6412 ey = dir.y > 0.0 ? rsy + 1.0f : -1.0f;
6405 // Direction vector is 0,0 so return 6413 }
6406 // false since we're staying in the sim 6414 else if(dir.y == 0.0f)
6407 return 0; 6415 {
6408 } 6416 ex = dir.x > 0 ? rsx + 1.0f : -1.0f;
6409 else 6417 ey = py;
6410 {
6411 // Y is the only valid direction
6412 edge.y = dir.y / Math.Abs(dir.y) * (World.RegionInfo.RegionSizeY / Constants.RegionSize);
6413 }
6414 } 6418 }
6415 else 6419 else
6416 { 6420 {
6417 LSL_Float mag; 6421 float dx = (float) dir.x;
6418 if (dir.x > 0) 6422 float dy = (float) dir.y;
6419 {
6420 mag = (World.RegionInfo.RegionSizeX - pos.x) / dir.x;
6421 }
6422 else
6423 {
6424 mag = (pos.x/dir.x);
6425 }
6426 6423
6427 mag = Math.Abs(mag); 6424 float t1 = dx * dx + dy * dy;
6425 dx /= t1;
6426 dy /= t1;
6428 6427
6429 edge.y = pos.y + (dir.y * mag); 6428 if(dx > 0)
6430 6429 t1 = (rsx + 1f - px)/dx;
6431 if (edge.y > World.RegionInfo.RegionSizeY || edge.y < 0)
6432 {
6433 // Y goes out of bounds first
6434 edge.y = dir.y / Math.Abs(dir.y) * (World.RegionInfo.RegionSizeY / Constants.RegionSize);
6435 }
6436 else 6430 else
6437 { 6431 t1 = -(px + 1f)/dx;
6438 // X goes out of bounds first or its a corner exit
6439 edge.y = 0;
6440 edge.x = dir.x / Math.Abs(dir.x) * (World.RegionInfo.RegionSizeY / Constants.RegionSize);
6441 }
6442 }
6443 6432
6444 List<GridRegion> neighbors = World.GridService.GetNeighbours(World.RegionInfo.ScopeID, World.RegionInfo.RegionID);
6445 6433
6446 uint neighborX = World.RegionInfo.RegionLocX + (uint)edge.x; 6434 float t2;
6447 uint neighborY = World.RegionInfo.RegionLocY + (uint)edge.y; 6435 if(dy > 0)
6436 t2 = (rsy + 1f - py)/dy;
6437 else
6438 t2 = -(py + 1f)/dy;
6448 6439
6449 foreach (GridRegion sri in neighbors) 6440 if(t1 > t2)
6450 { 6441 t1 = t2;
6451 if (sri.RegionCoordX == neighborX && sri.RegionCoordY == neighborY) 6442
6452 return 0; 6443 ex = px + t1 * dx;
6444 ey = py + t1 * dy;
6453 } 6445 }
6454 6446
6447 ex += World.RegionInfo.WorldLocX;
6448 ey += World.RegionInfo.WorldLocY;
6449
6450 if(World.GridService.GetRegionByPosition(World.RegionInfo.ScopeID, (int)ex, (int)ey) != null)
6451 return 0;
6455 return 1; 6452 return 1;
6456 } 6453 }
6457 6454