change caps get mesh and texture throttle
parent
7eeaee631d
commit
57ec0d3884
|
@ -100,7 +100,6 @@ namespace OpenSim.Capabilities.Handlers
|
||||||
Hashtable responsedata = new Hashtable();
|
Hashtable responsedata = new Hashtable();
|
||||||
responsedata["int_response_code"] = 400; //501; //410; //404;
|
responsedata["int_response_code"] = 400; //501; //410; //404;
|
||||||
responsedata["content_type"] = "text/plain";
|
responsedata["content_type"] = "text/plain";
|
||||||
responsedata["str_response_string"] = "Request wasn't what was expected";
|
|
||||||
responsedata["int_bytes"] = 0;
|
responsedata["int_bytes"] = 0;
|
||||||
|
|
||||||
string meshStr = string.Empty;
|
string meshStr = string.Empty;
|
||||||
|
@ -125,7 +124,6 @@ namespace OpenSim.Capabilities.Handlers
|
||||||
{
|
{
|
||||||
if (mesh.Type == (SByte)AssetType.Mesh)
|
if (mesh.Type == (SByte)AssetType.Mesh)
|
||||||
{
|
{
|
||||||
|
|
||||||
Hashtable headers = new Hashtable();
|
Hashtable headers = new Hashtable();
|
||||||
responsedata["headers"] = headers;
|
responsedata["headers"] = headers;
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,6 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
|
|
||||||
private Dictionary<UUID, PollServiceMeshEventArgs> m_pollservices = new Dictionary<UUID, PollServiceMeshEventArgs>();
|
private Dictionary<UUID, PollServiceMeshEventArgs> m_pollservices = new Dictionary<UUID, PollServiceMeshEventArgs>();
|
||||||
|
|
||||||
|
|
||||||
#region Region Module interfaceBase Members
|
#region Region Module interfaceBase Members
|
||||||
|
|
||||||
public Type ReplaceableInterface
|
public Type ReplaceableInterface
|
||||||
|
@ -134,7 +133,6 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
|
|
||||||
s.EventManager.OnRegisterCaps -= RegisterCaps;
|
s.EventManager.OnRegisterCaps -= RegisterCaps;
|
||||||
s.EventManager.OnDeregisterCaps -= DeregisterCaps;
|
s.EventManager.OnDeregisterCaps -= DeregisterCaps;
|
||||||
s.EventManager.OnThrottleUpdate -= ThrottleUpdate;
|
|
||||||
m_NumberScenes--;
|
m_NumberScenes--;
|
||||||
m_scene = null;
|
m_scene = null;
|
||||||
}
|
}
|
||||||
|
@ -153,7 +151,6 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
|
|
||||||
s.EventManager.OnRegisterCaps += RegisterCaps;
|
s.EventManager.OnRegisterCaps += RegisterCaps;
|
||||||
s.EventManager.OnDeregisterCaps += DeregisterCaps;
|
s.EventManager.OnDeregisterCaps += DeregisterCaps;
|
||||||
s.EventManager.OnThrottleUpdate += ThrottleUpdate;
|
|
||||||
|
|
||||||
m_NumberScenes++;
|
m_NumberScenes++;
|
||||||
|
|
||||||
|
@ -212,18 +209,6 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we know when the throttle is changed by the client in the case of a root agent or by a neighbor region in the case of a child agent.
|
|
||||||
public void ThrottleUpdate(ScenePresence p)
|
|
||||||
{
|
|
||||||
UUID user = p.UUID;
|
|
||||||
int imagethrottle = p.ControllingClient.GetAgentThrottleSilent((int)ThrottleOutPacketType.Asset);
|
|
||||||
PollServiceMeshEventArgs args;
|
|
||||||
if (m_pollservices.TryGetValue(user, out args))
|
|
||||||
{
|
|
||||||
args.UpdateThrottle(imagethrottle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PollServiceMeshEventArgs : PollServiceEventArgs
|
private class PollServiceMeshEventArgs : PollServiceEventArgs
|
||||||
{
|
{
|
||||||
private List<Hashtable> requests =
|
private List<Hashtable> requests =
|
||||||
|
@ -233,18 +218,28 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
private HashSet<UUID> dropedResponses = new HashSet<UUID>();
|
private HashSet<UUID> dropedResponses = new HashSet<UUID>();
|
||||||
|
|
||||||
private Scene m_scene;
|
private Scene m_scene;
|
||||||
private MeshCapsDataThrottler m_throttler;
|
private ScenePresence m_presence;
|
||||||
public PollServiceMeshEventArgs(string uri, UUID pId, Scene scene) :
|
public PollServiceMeshEventArgs(string uri, UUID pId, Scene scene) :
|
||||||
base(null, uri, null, null, null, null, pId, int.MaxValue)
|
base(null, uri, null, null, null, null, pId, int.MaxValue)
|
||||||
{
|
{
|
||||||
m_scene = scene;
|
m_scene = scene;
|
||||||
m_throttler = new MeshCapsDataThrottler(100000);
|
|
||||||
// x is request id, y is userid
|
// x is request id, y is userid
|
||||||
HasEvents = (x, y) =>
|
HasEvents = (x, y) =>
|
||||||
{
|
{
|
||||||
lock (responses)
|
lock (responses)
|
||||||
{
|
{
|
||||||
return m_throttler.hasEvents(x, responses);
|
APollResponse response;
|
||||||
|
if (responses.TryGetValue(x, out response))
|
||||||
|
{
|
||||||
|
if (m_presence == null)
|
||||||
|
m_presence = m_scene.GetScenePresence(pId);
|
||||||
|
|
||||||
|
if (m_presence == null || m_presence.IsDeleted)
|
||||||
|
return true;
|
||||||
|
return m_presence.CapCanSendAsset(1, response.bytes);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -269,7 +264,6 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
responses.Remove(x);
|
responses.Remove(x);
|
||||||
m_throttler.PassTime();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -282,7 +276,6 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
reqinfo.request = y;
|
reqinfo.request = y;
|
||||||
|
|
||||||
m_queue.Add(reqinfo);
|
m_queue.Add(reqinfo);
|
||||||
m_throttler.PassTime();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// this should never happen except possible on shutdown
|
// this should never happen except possible on shutdown
|
||||||
|
@ -307,7 +300,7 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
|
|
||||||
public void Process(APollRequest requestinfo)
|
public void Process(APollRequest requestinfo)
|
||||||
{
|
{
|
||||||
Hashtable response;
|
Hashtable curresponse;
|
||||||
|
|
||||||
UUID requestID = requestinfo.reqID;
|
UUID requestID = requestinfo.reqID;
|
||||||
|
|
||||||
|
@ -328,19 +321,17 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
// If the avatar is gone, don't bother to get the texture
|
// If the avatar is gone, don't bother to get the texture
|
||||||
if(m_scene.GetScenePresence(Id) == null)
|
if(m_scene.GetScenePresence(Id) == null)
|
||||||
{
|
{
|
||||||
response = new Hashtable();
|
curresponse = new Hashtable();
|
||||||
|
curresponse["int_response_code"] = 500;
|
||||||
response["int_response_code"] = 500;
|
curresponse["str_response_string"] = "Script timeout";
|
||||||
response["str_response_string"] = "Script timeout";
|
curresponse["content_type"] = "text/plain";
|
||||||
response["content_type"] = "text/plain";
|
curresponse["keepalive"] = false;
|
||||||
response["keepalive"] = false;
|
responses[requestID] = new APollResponse() { bytes = 0, response = curresponse };
|
||||||
responses[requestID] = new APollResponse() { bytes = 0, response = response};
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response = m_getMeshHandler.Handle(requestinfo.request);
|
curresponse = m_getMeshHandler.Handle(requestinfo.request);
|
||||||
|
|
||||||
lock(responses)
|
lock(responses)
|
||||||
{
|
{
|
||||||
|
@ -355,20 +346,10 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
|
|
||||||
responses[requestID] = new APollResponse()
|
responses[requestID] = new APollResponse()
|
||||||
{
|
{
|
||||||
bytes = (int)response["int_bytes"],
|
bytes = (int)curresponse["int_bytes"],
|
||||||
response = response
|
response = curresponse
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
m_throttler.PassTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void UpdateThrottle(int pthrottle)
|
|
||||||
{
|
|
||||||
int tmp = 2 * pthrottle;
|
|
||||||
if(tmp < 10000)
|
|
||||||
tmp = 10000;
|
|
||||||
m_throttler.ThrottleBytes = tmp;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,52 +400,5 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
m_pollservices.Remove(agentID);
|
m_pollservices.Remove(agentID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class MeshCapsDataThrottler
|
|
||||||
{
|
|
||||||
private double lastTimeElapsed = 0;
|
|
||||||
private double BytesSent = 0;
|
|
||||||
|
|
||||||
public MeshCapsDataThrottler(int pBytes)
|
|
||||||
{
|
|
||||||
if(pBytes < 10000)
|
|
||||||
pBytes = 10000;
|
|
||||||
ThrottleBytes = pBytes;
|
|
||||||
lastTimeElapsed = Util.GetTimeStamp();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool hasEvents(UUID key, Dictionary<UUID, APollResponse> responses)
|
|
||||||
{
|
|
||||||
PassTime();
|
|
||||||
APollResponse response;
|
|
||||||
if (responses.TryGetValue(key, out response))
|
|
||||||
{
|
|
||||||
// Normal
|
|
||||||
if (response.bytes == 0 || BytesSent <= ThrottleBytes)
|
|
||||||
{
|
|
||||||
BytesSent += response.bytes;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PassTime()
|
|
||||||
{
|
|
||||||
double currenttime = Util.GetTimeStamp();
|
|
||||||
double timeElapsed = currenttime - lastTimeElapsed;
|
|
||||||
if(timeElapsed < .05)
|
|
||||||
return;
|
|
||||||
int add = (int)(ThrottleBytes * timeElapsed);
|
|
||||||
if (add >= 1000)
|
|
||||||
{
|
|
||||||
lastTimeElapsed = currenttime;
|
|
||||||
BytesSent -= add;
|
|
||||||
if (BytesSent < 0) BytesSent = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int ThrottleBytes;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,7 +113,6 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
{
|
{
|
||||||
s.EventManager.OnRegisterCaps -= RegisterCaps;
|
s.EventManager.OnRegisterCaps -= RegisterCaps;
|
||||||
s.EventManager.OnDeregisterCaps -= DeregisterCaps;
|
s.EventManager.OnDeregisterCaps -= DeregisterCaps;
|
||||||
s.EventManager.OnThrottleUpdate -= ThrottleUpdate;
|
|
||||||
m_NumberScenes--;
|
m_NumberScenes--;
|
||||||
m_scene = null;
|
m_scene = null;
|
||||||
}
|
}
|
||||||
|
@ -129,7 +128,6 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
|
|
||||||
s.EventManager.OnRegisterCaps += RegisterCaps;
|
s.EventManager.OnRegisterCaps += RegisterCaps;
|
||||||
s.EventManager.OnDeregisterCaps += DeregisterCaps;
|
s.EventManager.OnDeregisterCaps += DeregisterCaps;
|
||||||
s.EventManager.OnThrottleUpdate += ThrottleUpdate;
|
|
||||||
|
|
||||||
m_NumberScenes++;
|
m_NumberScenes++;
|
||||||
|
|
||||||
|
@ -150,19 +148,6 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we know when the throttle is changed by the client in the case of a root agent or by a neighbor region in the case of a child agent.
|
|
||||||
public void ThrottleUpdate(ScenePresence p)
|
|
||||||
{
|
|
||||||
byte[] throttles = p.ControllingClient.GetThrottlesPacked(1);
|
|
||||||
UUID user = p.UUID;
|
|
||||||
int imagethrottle = p.ControllingClient.GetAgentThrottleSilent((int)ThrottleOutPacketType.Texture);
|
|
||||||
PollServiceTextureEventArgs args;
|
|
||||||
if (m_pollservices.TryGetValue(user,out args))
|
|
||||||
{
|
|
||||||
args.UpdateThrottle(imagethrottle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PostInitialise()
|
public void PostInitialise()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -198,20 +183,27 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
private HashSet<UUID> dropedResponses = new HashSet<UUID>();
|
private HashSet<UUID> dropedResponses = new HashSet<UUID>();
|
||||||
|
|
||||||
private Scene m_scene;
|
private Scene m_scene;
|
||||||
private CapsDataThrottler m_throttler;
|
private ScenePresence m_presence;
|
||||||
public PollServiceTextureEventArgs(UUID pId, Scene scene) :
|
public PollServiceTextureEventArgs(UUID pId, Scene scene) :
|
||||||
base(null, "", null, null, null, null, pId, int.MaxValue)
|
base(null, "", null, null, null, null, pId, int.MaxValue)
|
||||||
{
|
{
|
||||||
m_scene = scene;
|
m_scene = scene;
|
||||||
m_throttler = new CapsDataThrottler(100000);
|
|
||||||
// x is request id, y is userid
|
// x is request id, y is userid
|
||||||
HasEvents = (x, y) =>
|
HasEvents = (x, y) =>
|
||||||
{
|
{
|
||||||
lock (responses)
|
lock (responses)
|
||||||
{
|
{
|
||||||
bool ret = m_throttler.hasEvents(x, responses);
|
APollResponse response;
|
||||||
return ret;
|
if (responses.TryGetValue(x, out response))
|
||||||
|
{
|
||||||
|
if (m_presence == null)
|
||||||
|
m_presence = m_scene.GetScenePresence(pId);
|
||||||
|
|
||||||
|
if (m_presence == null || m_presence.IsDeleted)
|
||||||
|
return true;
|
||||||
|
return m_presence.CapCanSendAsset(0, response.bytes);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -235,7 +227,6 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
responses.Remove(x);
|
responses.Remove(x);
|
||||||
m_throttler.PassTime();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -260,7 +251,6 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_queue.Add(reqinfo);
|
m_queue.Add(reqinfo);
|
||||||
m_throttler.PassTime();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// this should never happen except possible on shutdown
|
// this should never happen except possible on shutdown
|
||||||
|
@ -346,7 +336,6 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
if(dropedResponses.Contains(requestID))
|
if(dropedResponses.Contains(requestID))
|
||||||
{
|
{
|
||||||
dropedResponses.Remove(requestID);
|
dropedResponses.Remove(requestID);
|
||||||
m_throttler.PassTime();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -356,15 +345,6 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
response = response
|
response = response
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
m_throttler.PassTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void UpdateThrottle(int pimagethrottle)
|
|
||||||
{
|
|
||||||
int tmp = 2 * pimagethrottle;
|
|
||||||
if(tmp < 10000)
|
|
||||||
tmp = 10000;
|
|
||||||
m_throttler.ThrottleBytes = tmp;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -437,49 +417,5 @@ namespace OpenSim.Region.ClientStack.Linden
|
||||||
poolreq.thepoll.Process(poolreq);
|
poolreq.thepoll.Process(poolreq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class CapsDataThrottler
|
|
||||||
{
|
|
||||||
private double lastTimeElapsed = 0;
|
|
||||||
private volatile int BytesSent = 0;
|
|
||||||
public CapsDataThrottler(int pBytes)
|
|
||||||
{
|
|
||||||
if(pBytes < 10000)
|
|
||||||
pBytes = 10000;
|
|
||||||
ThrottleBytes = pBytes;
|
|
||||||
lastTimeElapsed = Util.GetTimeStamp();
|
|
||||||
}
|
|
||||||
public bool hasEvents(UUID key, Dictionary<UUID, GetTextureModule.APollResponse> responses)
|
|
||||||
{
|
|
||||||
PassTime();
|
|
||||||
// Note, this is called IN LOCK
|
|
||||||
GetTextureModule.APollResponse response;
|
|
||||||
if (responses.TryGetValue(key, out response))
|
|
||||||
{
|
|
||||||
if (response.bytes == 0 || BytesSent <= ThrottleBytes)
|
|
||||||
{
|
|
||||||
BytesSent += response.bytes;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PassTime()
|
|
||||||
{
|
|
||||||
double currenttime = Util.GetTimeStamp();
|
|
||||||
double timeElapsed = currenttime - lastTimeElapsed;
|
|
||||||
if(timeElapsed < .05)
|
|
||||||
return;
|
|
||||||
int add = (int)(ThrottleBytes * timeElapsed);
|
|
||||||
if (add >= 1000)
|
|
||||||
{
|
|
||||||
lastTimeElapsed = currenttime;
|
|
||||||
BytesSent -= add;
|
|
||||||
if (BytesSent < 0) BytesSent = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public int ThrottleBytes;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1118,6 +1118,20 @@ namespace OpenSim.Region.Framework.Scenes
|
||||||
m_LandingPointBehavior = LandingPointBehavior.SL;
|
m_LandingPointBehavior = LandingPointBehavior.SL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_bandwidth = 100000;
|
||||||
|
m_lastBandwithTime = Util.GetTimeStamp() + 0.1;
|
||||||
|
IConfig cconfig = m_scene.Config.Configs["ClientStack.LindenCaps"];
|
||||||
|
if (cconfig != null)
|
||||||
|
{
|
||||||
|
m_capbandwidth = cconfig.GetInt("Cap_AssetThrottle", m_capbandwidth);
|
||||||
|
if(m_capbandwidth > 0)
|
||||||
|
{
|
||||||
|
m_bandwidth = m_capbandwidth;
|
||||||
|
if(m_bandwidth < 50000)
|
||||||
|
m_bandwidth = 50000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_bandwidthBurst = m_bandwidth / 5;
|
||||||
ControllingClient.RefreshGroupMembership();
|
ControllingClient.RefreshGroupMembership();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4626,7 +4640,12 @@ namespace OpenSim.Region.Framework.Scenes
|
||||||
|
|
||||||
private void RaiseUpdateThrottles()
|
private void RaiseUpdateThrottles()
|
||||||
{
|
{
|
||||||
m_scene.EventManager.TriggerThrottleUpdate(this);
|
if(m_capbandwidth > 0)
|
||||||
|
return;
|
||||||
|
m_bandwidth = 4 * ControllingClient.GetAgentThrottleSilent((int)ThrottleOutPacketType.Texture);
|
||||||
|
if(m_bandwidth < 50000)
|
||||||
|
m_bandwidth = 50000;
|
||||||
|
m_bandwidthBurst = m_bandwidth / 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -6830,5 +6849,42 @@ namespace OpenSim.Region.Framework.Scenes
|
||||||
{
|
{
|
||||||
return Overrides.GetOverriddenAnimation(animState);
|
return Overrides.GetOverriddenAnimation(animState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// http caps assets bandwidth control
|
||||||
|
private int m_capbandwidth = -1;
|
||||||
|
private int m_bandwidth = 100000;
|
||||||
|
private int m_bandwidthBurst = 20000;
|
||||||
|
private int m_bytesControl;
|
||||||
|
private double m_lastBandwithTime;
|
||||||
|
|
||||||
|
public bool CapCanSendAsset(int type, int size)
|
||||||
|
{
|
||||||
|
if(size == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if(type > 1)
|
||||||
|
{
|
||||||
|
// not texture or mesh
|
||||||
|
m_bytesControl -= size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
double currenttime = Util.GetTimeStamp();
|
||||||
|
double timeElapsed = currenttime - m_lastBandwithTime;
|
||||||
|
if (timeElapsed > .05)
|
||||||
|
{
|
||||||
|
m_lastBandwithTime = currenttime;
|
||||||
|
int add = (int)(m_bandwidth * timeElapsed);
|
||||||
|
m_bytesControl += add;
|
||||||
|
if (m_bytesControl > m_bandwidthBurst)
|
||||||
|
m_bytesControl = m_bandwidthBurst;
|
||||||
|
}
|
||||||
|
if (m_bytesControl > 0 )
|
||||||
|
{
|
||||||
|
m_bytesControl -= size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue