【发布时间】:2021-10-18 02:57:29
【问题描述】:
我目前正在开发一个使用 OpenAI 的 GPT3 为 NPC 对话提供支持的项目。每个 NPC 在对话时会向我的服务器发送一个 POST 请求,该请求会返回 NPC 通过其本地 AudioSource 播放的音频文件。
视频在这里:https://www.youtube.com/watch?v=pygM6yDE9hI
我遇到的一个问题是抖动和短暂的延迟。
我是 Unity 的新手,但根据分析器,我认为罪魁祸首是处理网络请求的协程。
下面是我的这个功能的代码:
// Update is called once per frame
void Update()
{
int index = -1;
if (conversation != null)
{
if (!conversation.processing)
{
if (conversation.currentSpeaker.Equals(this.id))
{
Debug.Log(this.id + " getting response");
conversation.processing = true;
StartCoroutine(getResponse(conversation));
}
}
}
else if (Datastore.Instance.id2conversation.TryGetValue(id, out index))
{
conversation = Datastore.Instance.conversations[index];
}
}
IEnumerator getResponse(Conversation conversation)
{
WWWForm form = new WWWForm();
form.AddField("id", this.id);
var www = UnityWebRequest.Post("http://" + Datastore.Instance.host + ":3000/generate", form);
yield return www.SendWebRequest();
if (interrupted) yield break;
if (www.isNetworkError)
{
Debug.Log(www.error);
}
else
{
if (www.GetResponseHeaders().Count > 0)
{
var jsonData = JSON.Parse(www.downloadHandler.text);
string stringData = jsonData["audioContent"]["data"].ToString();
byte[] rawdata = AudioHelpers.ConvertToByteStream(stringData);
AudioClip clip = AudioHelpers.ConvertToAudioClip(rawdata);
this.audioSource.clip = clip;
this.audioSource.Play();
this.animator.SetBool(this.talkingBoolHash, true);
Debug.Log("Response Recieved");
yield return new WaitForSeconds(clip.length);
if (interrupted) yield break;
this.conversation.currentSpeaker = jsonData["nextSpeaker"].ToString().Replace("\"", "");
this.conversation.processing = false;
this.animator.SetBool(this.talkingBoolHash, false);
}
}
}
如何提高此代码的性能以消除帧速率下降的时期?
是否可以通过 Unity Jobs 将此代码移动到另一个线程?
任何帮助将不胜感激。
为 JSON 解析实现了线程。新代码:
// Update is called once per frame
void Update()
{
int index = -1;
if (conversation != null)
{
if (!conversation.processing)
{
if (conversation.currentSpeaker.Equals(this.id))
{
Debug.Log(this.id + " getting response");
conversation.processing = true;
StartCoroutine(getResponse(conversation));
}
}
}
else if (Datastore.Instance.id2conversation.TryGetValue(id, out index))
{
conversation = Datastore.Instance.conversations[index];
}
}
Task<(byte[], string)> ParseAudioData(string rawJson)
{
try
{
return Task.Run(() => {
var jsonData = JSON.Parse(rawJson);
string stringData = jsonData["audioContent"]["data"].ToString();
byte[] rawdata = AudioHelpers.ConvertToByteStream(stringData);
string nextSpeaker = jsonData["nextSpeaker"].ToString().Replace("\"", "");
return Task.FromResult((rawdata, nextSpeaker));
});
}
catch(Exception e)
{
Debug.LogException(e);
throw;
}
}
IEnumerator getResponse(Conversation conversation)
{
WWWForm form = new WWWForm();
form.AddField("id", this.id);
var www = UnityWebRequest.Post("http://" + Datastore.Instance.host + ":3000/generate", form);
yield return www.SendWebRequest();
if (interrupted) yield break;
if (www.isNetworkError)
{
Debug.Log(www.error);
}
else
{
if (www.GetResponseHeaders().Count > 0)
{
Task<(byte[], string)> t = ParseAudioData(www.downloadHandler.text);
yield return t;
AudioClip clip = AudioHelpers.ConvertToAudioClip(t.Result.Item1);
this.audioSource.clip = clip;
this.audioSource.Play();
this.animator.SetBool(this.talkingBoolHash, true);
Debug.Log("Response Recieved");
yield return new WaitForSeconds(clip.length);
if (interrupted) yield break;
this.conversation.currentSpeaker = t.Result.Item2;
this.conversation.processing = false;
this.animator.SetBool(this.talkingBoolHash, false);
}
}
}
最终编辑:在下面的答案的帮助下得到了它!
我的最终代码:
Task<JSONNode> ParseJsonData(string rawJson)
{
try
{
return Task.Run(() =>
{
JSONNode jsonData = JSON.Parse(rawJson);
return jsonData;
});
}
catch (Exception e)
{
Debug.LogException(e);
throw;
}
}
Task<(byte[], string)> ParseAudioData(JSONNode jsonData)
{
try
{
return Task.Run(() => {
string stringData = jsonData["audioContent"]["data"].ToString();
byte[] rawdata = AudioHelpers.ConvertToByteStream(stringData);
string nextSpeaker = jsonData["nextSpeaker"].ToString().Replace("\"", "");
return (rawdata, nextSpeaker);
});
}
catch (Exception e)
{
Debug.LogException(e);
throw;
}
}
IEnumerator PlayDialog(AudioClip clip, string nextSpeaker)
{
this.audioSource.clip = clip;
this.audioSource.Play();
this.animator.SetBool(this.talkingBoolHash, true);
yield return new WaitForSeconds(clip.length);
if (interrupted) yield break;
this.conversation.currentSpeaker = nextSpeaker;
this.conversation.processing = false;
this.animator.SetBool(this.talkingBoolHash, false);
}
IEnumerator getResponse(Conversation conversation)
{
WWWForm form = new WWWForm();
form.AddField("id", this.id);
var www = UnityWebRequest.Post("http://" + Datastore.Instance.host + ":3000/generate", form);
yield return www.SendWebRequest();
if (interrupted) yield break;
if (www.isNetworkError)
{
Debug.Log(www.error);
}
else
{
if (www.GetResponseHeaders().Count > 0)
{
ParseJsonData(www.downloadHandler.text).ContinueWith((jsonData) => {
ParseAudioData(jsonData.Result).ContinueWith((t) =>
{
// https://github.com/PimDeWitte/UnityMainThreadDispatcher
UnityMainThreadDispatcher.Instance().Enqueue(() =>
{
AudioClip clip = AudioHelpers.ConvertToAudioClip(t.Result.Item1);
StartCoroutine(PlayDialog(clip, t.Result.Item2));
Debug.Log("Response Recieved");
});
});
});
}
}
}
【问题讨论】:
-
不理解
return Task.FromResult。由于我不会进入的原因,通常应该避免这种方法。您是否尝试使用以下答案?如果是,有什么问题?这在我的脑海中有点难以理解,但你不想阻止在后台运行的任务。 -
@Zer0 我无法直接复制并粘贴下面的答案,因为 ContinueWith 中的代码在范围内没有 rawdata 变量。此外,当我通过一些小的调整让它工作时,什么都没有发生,我天真地假设这是因为你不允许在其他线程中调用 Unity API。对 C# 语法仍然非常缺乏经验。应该使用什么来代替 Task.FromResult?
-
尝试将
TaskScheduler.FromCurrentSynchronizationContext()作为ContinueWith的参数,让它在主线程上运行。我不熟悉 Unity,所以如果这不起作用,有人可以纠正我。如果没有,可以试试async await吗? -
@Zer0 你对 TaskScheduler.FromCurrentSynchronizationContext() 有什么建议吗?之前没见过,不太明白怎么实现。
标签: c# unity3d optimization task