Как заставить corutine ждать загрузки (Unity Android)?

#android #unity3d

#Android #unity3d

Вопрос:

При первом запуске приложения мне нужно загрузить файл размером 100 МБ. Для этого я использую такой подход:

     protected void Start()
    {
        if (!File.Exists(toCopy))
        {
            StartCoroutine(CopyFile(fromCopy, toCopy));
        }
...
    }

    IEnumerator CopyFile(string from, string to)
    {
        WWW www = new WWW(from);

        while (!www.isDone)
        {
            yield return null;
        }

        byte[] yourBytes = www.bytes;

        File.WriteAllBytes(to, yourBytes);
    }
  

Но мое приложение разбилось, и загруженный файл на самом деле имеет размер — 0 КБ. Итак, я предполагаю, что corutine начинает загрузку, но ожидания нет, и далее в коде я пытаюсь использовать этот файл, но на самом деле он не был загружен, поэтому я получаю сбой.

Вопрос в том, как заставить corutine в этом случае ждать, пока файл не будет загружен?

Редактировать

Я меняю свой код, спасибо @derHugo

     IEnumerator CopyFile(string from, string to)
    {
        using (var uwr = UnityWebRequest.Get(from))
        {
            yield return uwr.SendWebRequest();

            if (uwr.isHttpError || uwr.isNetworkError)
            {
                Debug.LogError($"Unity Download error :: {uwr.responseCode} - {uwr.error}!", this);
                yield break;
            }

            byte[] yourBytes = uwr.downloadHandler.data;

            File.WriteAllBytes(to, yourBytes);
        }
    }

protected void Start()
{
...
        if (!File.Exists(toCopy))
        {
            StartCoroutine(CopyFile(fromCopy, toCopy));
        }

        useCoiedFileByPath(toCopy); <-------------- //Can I to be sure that on this line file has alredy copied and available to use?

...
}
  

ПРАВКА2

     protected IEnumerator Start()
    {
        Debug.LogError("Unity Begin");

#if !UNITY_ANDROID
        if (!clipFile.Contains("\"))
        {
            string resFolder = Path.GetFullPath(Application.streamingAssetsPath);
            clipFile = Path.GetFullPath(Path.Combine(resFolder, clipFile));
        }
#endif

        Debug.LogError("Unity Before copy");
#if UNITY_ANDROID
        string fromCopy = Path.Combine(Application.streamingAssetsPath, clipFile);
        string toCopy = Application.persistentDataPath   "/copiedfile.tet";
        Debug.LogError($"Unity HERE path from copy :: {fromCopy}, to copy :: {toCopy}");

        if (!File.Exists(toCopy)) 
        {
            yield return CopyFile(fromCopy, toCopy);
            //StartCoroutine(CopyFile(fromCopy, toCopy));
        }
#endif

        Debug.LogError("Unity After copy");

        register_debug_callback_default();
        stream_set_progiling_debug_messages(true);

#if UNITY_ANDROID
        clipFile = Application.persistentDataPath   "/"   clipFile;
#endif

        if (File.Exists(clipFile))
        {
            Debug.LogError($"Path to file :: {clipFile}");
            CreateStreamDecoder();
            stream_init_model(stream, clipFile);
        }
        else
        {
            stream = IntPtr.Zero;
        }

        meshRenderer = gameObject.AddComponent<MeshRenderer>();
        meshRenderer.material = new Material(GetShader());
        meshFilter = gameObject.AddComponent<MeshFilter>();
        FramePlaying = 0;

        Debug.LogError("Unity End");

        yield return null;
    }

    IEnumerator CopyFile(string from, string to)
    {
        using (var uwr = UnityWebRequest.Get(from))
        {
            yield return uwr.SendWebRequest();

            if (uwr.isHttpError || uwr.isNetworkError)
            {
                Debug.LogError($"Unity Download error :: {uwr.responseCode} - {uwr.error}!", this);
                yield break;
            }

            byte[] yourBytes = uwr.downloadHandler.data;

            File.WriteAllBytes(to, yourBytes);
        }
    }
  

Комментарии:

1. Www устарел, однако вы, похоже, никогда не запрашиваете его. Вы получаете готовый объект, но, похоже, ничего не запрашиваете

Ответ №1:

Afaik это должно сработать

 IEnumerator CopyFile(string from, string to)
{
    using(var www = new WWW(from))
    {
        yield return www;
        // though it should behave the same as
        //while(!www.isDone) yield return null;
        // or also
        //yield return new WaitUntil(() => www.isDone);

        byte[] yourBytes = www.bytes;

        File.WriteAllBytes(to, yourBytes);
    }
}
  

Однако в целом WWW он устарел и не имеет реальной проверки на наличие каких-либо ошибок в пути. Таким образом, у вас может быть просто скрытая ошибка из-за отсутствия местоположения файла, приводящего к пустому byte[] .

Вероятно, вам лучше использовать UnityWebRequest.Get

 IEnumerator CopyFile(string from, string to)
{
    using(var uwr = UnityWebRequest.Get(from))
    {
        yield return uwr.SendRequest();

        if(uwr.isHttpError || uwr.isNetworkError)
        {
             Debug.LogError($"Download error {uwr.responseCode} - {uwr.error}!", this);
            yield break;
        }

        byte[] yourBytes = uwr.downloadHandler.data;

        File.WriteAllBytes(to, yourBytes);
    }
}
  

Дело в том, что в вашем случае использования совершенно неясно, в какой момент именно ваше приложение выходит из строя и что приводит к этому сбою.


Обновить

Теперь, когда мы знаем вашу реальную проблему: нет!Вы не можете быть уверены, поскольку запуск сопрограммы не задержит последующие строки кода. useCoiedFilePath Будет выполнено немедленно.

Если вы хотите убедиться, что есть как минимум три возможных пути:

А) Просто создайте саму Start сопрограмму. Да, это возможно и, вероятно, является любимым для вашего варианта использования

 protected IEnumerator Start()
{
    ...

    if (!File.Exists(toCopy))
    {
        yield return CopyFile(fromCopy, toCopy));
    }

    useCoiedFileByPath(toCopy); 

    ...
}
  

Это просто задерживает все в конце Start до завершения загрузки.

Б) Переместите код, который будет отложен, в процедуру

 protected void Start()
{
    ...

    StartCoroutine(CopyFile(fromCopy, toCopy));
}

IEnumerator CopyFile(string from, string to)
{
    if(!File.Exists(to))
    {
        using (var uwr = UnityWebRequest.Get(from))
        {
            yield return uwr.SendWebRequest();

            if (uwr.isHttpError || uwr.isNetworkError)
            {
                Debug.LogError($"Unity Download error : {uwr.responseCode} - {uwr.error}!", this);
                yield break;
            }

            byte[] yourBytes = uwr.downloadHandler.data;

            File.WriteAllBytes(to, yourBytes);
        }
    }

    useCoiedFileByPath(toCopy);
    ...
}
  

Обратите внимание, однако, на то, что эта процедура копирования теперь становится довольно негибкой, поскольку проверка на существование и реакцию уже включена, поэтому ее нельзя повторно использовать позже, чтобы просто скопировать другой файл.

C) Используйте событие обратного вызова, например

 IEnumerator CopyFile(string from, string to, Action whenDone)
{
    using (var uwr = UnityWebRequest.Get(from))
    {
        yield return uwr.SendWebRequest();

        if (uwr.isHttpError || uwr.isNetworkError)
        {
            Debug.LogError($"Unity Download error :: {uwr.responseCode} - {uwr.error}!", this);
            yield break;
        }

        byte[] yourBytes = uwr.downloadHandler.data;

        File.WriteAllBytes(to, yourBytes);
    }

    whenDone?.Invoke();
}

protected void Start()
{
    ...

    if (!File.Exists(toCopy))
    {
        StartCoroutine(CopyFile(fromCopy, toCopy, () => 
        {
            useCoiedFileByPath(toCopy)));

            ...
        }
    }
    else
    {
        useCoiedFileByPath(toCopy);

        ...
    }
}
  

Это более гибко, чем B , но обратите внимание, как вам приходится как бы дважды реализовывать реакцию.

Комментарии:

1. Спасибо! Я отредактировал свой вопрос, и есть строка комментария Can I to be sure that on this line file has alredy copied and available to use? . Не могли бы вы взглянуть

2. Увидел редактирование, и ответ: нет, конечно, нет! Запуск сопрограммы не задержит ни одной строки кода после нее. Если вы хотите быть уверенным, переместите это в сопрограмму

3. Первый вариант действительно наиболее соответствует тому, что мне нужно. Я изменил свой Start() метод на реализацию, которую вы указали в своем ответе, но она не работает. Похоже, что Start() это не вызывает… Вы уверены, что protected void Start() и protected IEnumerator Start() вызывается системой таким же образом?

4. Не так, но да, так и должно быть. См., Например Start . Если Start возвращает IEnumerator Unity, он автоматически запускает его как сопрограмму

5. хм, я все еще не мог найти, в чем проблема. Я отредактировал свой вопрос. Не могли бы вы взглянуть, нормально ли это выглядит? Я что-то здесь упускаю?

Ответ №2:

Вам нужно вернуть свой класс www после реализации класса и после этого попытаться вернуть новый waitUntil(()=>www.isDone )

Комментарии:

1. Не могли бы вы привести пример кода? Я новичок в C # Unity, не уверен, что понял, что вы имели в виду