Added background thread to handle delayed send and save of appearance

to accommodate batching of the many updates that happen on login
and teleport.

Fixed handling of the serial property in appearance.
viewer-2-initial-appearance
Master ScienceSim 2010-10-28 09:00:39 -07:00
parent 9cfd3e1d5a
commit 0f28fa400d
2 changed files with 177 additions and 65 deletions

View File

@ -233,7 +233,7 @@ namespace OpenSim.Framework
// DEBUG ON // DEBUG ON
m_log.WarnFormat("[AVATAR APPEARANCE] create empty appearance for {0}",owner); m_log.WarnFormat("[AVATAR APPEARANCE] create empty appearance for {0}",owner);
// DEBUG OFF // DEBUG OFF
m_serial = 0; m_serial = 1;
m_owner = owner; m_owner = owner;
SetDefaultWearables(); SetDefaultWearables();
@ -289,7 +289,7 @@ namespace OpenSim.Framework
// DEBUG OFF // DEBUG OFF
if (appearance == null) if (appearance == null)
{ {
m_serial = 0; m_serial = 1;
m_owner = UUID.Zero; m_owner = UUID.Zero;
SetDefaultWearables(); SetDefaultWearables();
@ -467,6 +467,9 @@ namespace OpenSim.Framework
public override String ToString() public override String ToString()
{ {
String s = ""; String s = "";
s += String.Format("Serial: {0}\n",m_serial);
for (uint i = 0; i < AvatarAppearance.TEXTURE_COUNT; i++) for (uint i = 0; i < AvatarAppearance.TEXTURE_COUNT; i++)
if (m_texture.FaceTextures[i] != null) if (m_texture.FaceTextures[i] != null)
s += String.Format("Texture: {0} --> {1}\n",i,m_texture.FaceTextures[i].TextureID); s += String.Format("Texture: {0} --> {1}\n",i,m_texture.FaceTextures[i].TextureID);
@ -625,8 +628,8 @@ namespace OpenSim.Framework
/// </summary> /// </summary>
public void Unpack(OSDMap data) public void Unpack(OSDMap data)
{ {
if ((data != null) && (data["appearance_serial"] != null)) if ((data != null) && (data["serial"] != null))
m_serial = data["appearance_serial"].AsInteger(); m_serial = data["serial"].AsInteger();
if ((data != null) && (data["height"] != null)) if ((data != null) && (data["height"] != null))
m_avatarHeight = (float)data["height"].AsReal(); m_avatarHeight = (float)data["height"].AsReal();
if ((data != null) && (data["hipoffset"] != null)) if ((data != null) && (data["hipoffset"] != null))

View File

@ -32,6 +32,10 @@ using Nini.Config;
using OpenMetaverse; using OpenMetaverse;
using OpenSim.Framework; using OpenSim.Framework;
using System.Threading;
using System.Timers;
using System.Collections.Generic;
using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Scenes;
using OpenSim.Services.Interfaces; using OpenSim.Services.Interfaces;
@ -44,7 +48,15 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
private static readonly byte[] BAKE_INDICES = new byte[] { 8, 9, 10, 11, 19, 20 }; private static readonly byte[] BAKE_INDICES = new byte[] { 8, 9, 10, 11, 19, 20 };
private Scene m_scene = null; private Scene m_scene = null;
private bool m_startAnimationSet = false; private static readonly int m_savetime = 5; // seconds to wait before saving changed appearance
private static readonly int m_sendtime = 2; // seconds to wait before sending changed appearance
private static readonly int m_checkTime = 500; // milliseconds to wait between checks for appearance updates
private System.Timers.Timer m_updateTimer = new System.Timers.Timer();
private Dictionary<UUID,long> m_savequeue = new Dictionary<UUID,long>();
private Dictionary<UUID,long> m_sendqueue = new Dictionary<UUID,long>();
#region RegionModule Members
public void Initialise(Scene scene, IConfigSource source) public void Initialise(Scene scene, IConfigSource source)
{ {
@ -56,6 +68,10 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
public void PostInitialise() public void PostInitialise()
{ {
m_updateTimer.Enabled = false;
m_updateTimer.AutoReset = true;
m_updateTimer.Interval = m_checkTime; // 500 milliseconds wait to start async ops
m_updateTimer.Elapsed += new ElapsedEventHandler(HandleAppearanceUpdateTimer);
} }
public void Close() public void Close()
@ -84,14 +100,7 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
// client.OnAvatarNowWearing -= AvatarIsWearing; // client.OnAvatarNowWearing -= AvatarIsWearing;
} }
public void CheckBakedTextureAssets(IClientAPI client, UUID textureID, int idx) #endregion
{
if (m_scene.AssetService.Get(textureID.ToString()) == null)
{
m_log.WarnFormat("[AVFACTORY]: Missing baked texture {0} ({1}) for avatar {2}",textureID,idx,client.Name);
client.SendRebakeAvatarTextures(textureID);
}
}
/// <summary> /// <summary>
/// Set appearance data (textureentry and slider settings) received from the client /// Set appearance data (textureentry and slider settings) received from the client
@ -100,6 +109,10 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
/// <param name="visualParam"></param> /// <param name="visualParam"></param>
public void SetAppearance(IClientAPI client, Primitive.TextureEntry textureEntry, byte[] visualParams) public void SetAppearance(IClientAPI client, Primitive.TextureEntry textureEntry, byte[] visualParams)
{ {
// DEBUG ON
m_log.WarnFormat("[AVFACTORY] SetAppearance for {0}",client.AgentId);
// DEBUG OFF
ScenePresence sp = m_scene.GetScenePresence(client.AgentId); ScenePresence sp = m_scene.GetScenePresence(client.AgentId);
if (sp == null) if (sp == null)
{ {
@ -107,79 +120,175 @@ namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory
return; return;
} }
// DEBUG ON
m_log.WarnFormat("[AVFACTORY] SetAppearance for {0}",client.AgentId);
// DEBUG OFF
/*
if (m_physicsActor != null)
{
if (!IsChildAgent)
{
// This may seem like it's redundant, remove the avatar from the physics scene
// just to add it back again, but it saves us from having to update
// 3 variables 10 times a second.
bool flyingTemp = m_physicsActor.Flying;
RemoveFromPhysicalScene();
//m_scene.PhysicsScene.RemoveAvatar(m_physicsActor);
//PhysicsActor = null;
AddToPhysicalScene(flyingTemp);
}
}
*/
#region Bake Cache Check
bool changed = false; bool changed = false;
// Process the texture entry // Process the texture entry
if (textureEntry != null) if (textureEntry != null)
{ {
for (int i = 0; i < BAKE_INDICES.Length; i++)
{
int j = BAKE_INDICES[i];
Primitive.TextureEntryFace face = textureEntry.FaceTextures[j];
if (face != null && face.TextureID != AppearanceManager.DEFAULT_AVATAR_TEXTURE)
Util.FireAndForget(delegate(object o) { CheckBakedTextureAssets(client,face.TextureID,j); });
}
changed = sp.Appearance.SetTextureEntries(textureEntry); changed = sp.Appearance.SetTextureEntries(textureEntry);
for (int i = 0; i < BAKE_INDICES.Length; i++)
{
int idx = BAKE_INDICES[i];
Primitive.TextureEntryFace face = sp.Appearance.Texture.FaceTextures[idx];
if (face != null && face.TextureID != AppearanceManager.DEFAULT_AVATAR_TEXTURE)
Util.FireAndForget(delegate(object o) { CheckBakedTextureAssets(client,face.TextureID,idx); });
}
} }
#endregion Bake Cache Check // Process the visual params, this may change height as well
if (visualParams != null)
changed = sp.Appearance.SetVisualParams(visualParams) || changed;
// If nothing changed (this happens frequently) just return
if (changed)
{ {
// DEBUG ON if (sp.Appearance.SetVisualParams(visualParams))
m_log.Warn("[AVFACTORY] Appearance changed"); {
// DEBUG OFF changed = true;
sp.Appearance.SetAppearance(textureEntry, visualParams);
if (sp.Appearance.AvatarHeight > 0) if (sp.Appearance.AvatarHeight > 0)
sp.SetHeight(sp.Appearance.AvatarHeight); sp.SetHeight(sp.Appearance.AvatarHeight);
m_scene.AvatarService.SetAppearance(client.AgentId, sp.Appearance);
} }
}
// If something changed in the appearance then queue an appearance save
if (changed)
QueueAppearanceSave(client.AgentId);
// And always queue up an appearance update to send out
QueueAppearanceSend(client.AgentId);
// Send the appearance back to the avatar
AvatarAppearance avp = sp.Appearance;
sp.ControllingClient.SendAvatarDataImmediate(sp);
sp.ControllingClient.SendAppearance(avp.Owner,avp.VisualParams,avp.Texture.GetBytes());
}
/// <summary>
/// Checks for the existance of a baked texture asset and
/// requests the viewer rebake if the asset is not found
/// </summary>
/// <param name="client"></param>
/// <param name="textureID"></param>
/// <param name="idx"></param>
private void CheckBakedTextureAssets(IClientAPI client, UUID textureID, int idx)
{
if (m_scene.AssetService.Get(textureID.ToString()) == null)
{
m_log.WarnFormat("[AVFACTORY]: Missing baked texture {0} ({1}) for avatar {2}",
textureID,idx,client.Name);
client.SendRebakeAvatarTextures(textureID);
}
}
#region UpdateAppearanceTimer
public void QueueAppearanceSend(UUID agentid)
{
// DEBUG ON // DEBUG ON
else m_log.WarnFormat("[AVFACTORY] Queue appearance send for {0}",agentid);
m_log.Warn("[AVFACTORY] Appearance did not change");
// DEBUG OFF // DEBUG OFF
// 100 nanoseconds (ticks) we should wait
long timestamp = DateTime.Now.Ticks + Convert.ToInt64(m_sendtime * 10000000);
lock (m_sendqueue)
{
m_sendqueue[agentid] = timestamp;
m_updateTimer.Start();
}
}
public void QueueAppearanceSave(UUID agentid)
{
// DEBUG ON
m_log.WarnFormat("[AVFACTORY] Queue appearance save for {0}",agentid);
// DEBUG OFF
// 100 nanoseconds (ticks) we should wait
long timestamp = DateTime.Now.Ticks + Convert.ToInt64(m_savetime * 10000000);
lock (m_savequeue)
{
m_savequeue[agentid] = timestamp;
m_updateTimer.Start();
}
}
private void HandleAppearanceSend(UUID agentid)
{
ScenePresence sp = m_scene.GetScenePresence(agentid);
if (sp == null)
{
m_log.WarnFormat("[AVFACTORY] Agent {0} no longer in the scene",agentid);
return;
}
// DEBUG ON
m_log.WarnFormat("[AVFACTORY] Handle appearance send for {0}\n{1}",agentid,sp.Appearance.ToString());
// DEBUG OFF
// Send the appearance to everyone in the scene
sp.SendAppearanceToAllOtherAgents(); sp.SendAppearanceToAllOtherAgents();
// Send the appearance back to the avatar
AvatarAppearance avp = sp.Appearance;
sp.ControllingClient.SendAvatarDataImmediate(sp);
sp.ControllingClient.SendAppearance(avp.Owner,avp.VisualParams,avp.Texture.GetBytes());
/*
// this needs to be fixed, the flag should be on scene presence not the region module
// Start the animations if necessary
if (!m_startAnimationSet) if (!m_startAnimationSet)
{ {
sp.Animator.UpdateMovementAnimations(); sp.Animator.UpdateMovementAnimations();
m_startAnimationSet = true; m_startAnimationSet = true;
} }
*/
client.SendAvatarDataImmediate(sp);
client.SendAppearance(sp.Appearance.Owner,sp.Appearance.VisualParams,sp.Appearance.Texture.GetBytes());
} }
private void HandleAppearanceSave(UUID agentid)
{
ScenePresence sp = m_scene.GetScenePresence(agentid);
if (sp == null)
{
m_log.WarnFormat("[AVFACTORY] Agent {0} no longer in the scene",agentid);
return;
}
m_scene.AvatarService.SetAppearance(agentid, sp.Appearance);
}
private void HandleAppearanceUpdateTimer(object sender, EventArgs ea)
{
long now = DateTime.Now.Ticks;
lock (m_sendqueue)
{
Dictionary<UUID,long> sends = new Dictionary<UUID,long>(m_sendqueue);
foreach (KeyValuePair<UUID,long> kvp in sends)
{
if (kvp.Value < now)
{
Util.FireAndForget(delegate(object o) { HandleAppearanceSend(kvp.Key); });
m_sendqueue.Remove(kvp.Key);
}
}
}
lock (m_savequeue)
{
Dictionary<UUID,long> saves = new Dictionary<UUID,long>(m_savequeue);
foreach (KeyValuePair<UUID,long> kvp in saves)
{
if (kvp.Value < now)
{
Util.FireAndForget(delegate(object o) { HandleAppearanceSave(kvp.Key); });
m_savequeue.Remove(kvp.Key);
}
}
}
if (m_savequeue.Count == 0 && m_sendqueue.Count == 0)
m_updateTimer.Stop();
}
#endregion
/// <summary> /// <summary>
/// Tell the client for this scene presence what items it should be wearing now /// Tell the client for this scene presence what items it should be wearing now
/// </summary> /// </summary>