Search Unity

Busy polling Yuck!

Discussion in 'Scripting' started by Marc, Nov 22, 2007.

  1. Marc

    Marc

    Joined:
    Oct 4, 2007
    Posts:
    499
    Hi im trying to create a single object which is responsible for all downloading of textures a sort of proxy if you will (named TextureFetcher in the examples). All other objects who has textures can ask the TextureFecther to fecth a texture which they in turn can apply to their material.

    The TextureFetcher code looks something like:

    Code (csharp):
    1.  
    2. public class TextureFetcher : MonoBehaviour {
    3.  
    4.    public Texture GetTexture(string url) {
    5.        
    6.       Texture ret = null;
    7.  
    8.       WWW www = new WWW(url);
    9.  
    10.       if([url]www.error[/url] == null)
    11.          ret = [url]www.texture;[/url]
    12.  
    13.       return ret;
    14.  
    15.    }
    16.  
    17. }
    18.  
    Problem is this will not work (unless the url refers to a file on disk using the file:// syntax) as the texture is not garantueed to be completely downloaded when the next instruction executes (it probably has not even started downloading yet?). From what i read in the reference manual WWW should either be yielded in a coroutine or busy polled for IsDone().

    I tried creating a Coroutine for the job to see if i could get the TextureFetcher to to work. Looks like:

    Code (csharp):
    1.  
    2. public class TextureFetcher : MonoBehaviour {
    3.  
    4.    public Texture GetTexture(string url) {
    5.        
    6.       Texture ret = null;
    7.  
    8.       StartCoroutine(DownloadTexture(url));
    9.  
    10.       return ret;
    11.  
    12.    }
    13.  
    14.    public IEnumerator DownloadTexture(string url) {
    15.        
    16.       WWW www = new WWW(url);
    17.       yield return www;
    18.  
    19.       if([url]www.error[/url] == null) {
    20.          // Now the texture is downloaded!
    21.          [url]www.texture;[/url]
    22.       }
    23.  
    24.       // can´t return texture here cause of IEnumerator return type.
    25.  
    26.    }
    27.  
    28. }
    29.  
    Now the texture will be downloaded without busy polling, BUT i cannot make the method which started the download return the newly downloaded texture, so what to do next?

    Make a private class variable set that from the coroutine when downloading has completed and return that.

    Code (csharp):
    1.  
    2. public class TextureFetcher : MonoBehaviour {
    3.  
    4.    private Texture m_texture = null;
    5.  
    6.    public Texture GetTexture(string url) {
    7.  
    8.       StartCoroutine(DownloadTexture(url));
    9.  
    10.       return m_texture ;
    11.  
    12.    }
    13.  
    14.    public IEnumerator DownloadTexture(string url) {
    15.        
    16.       WWW www = new WWW(url);
    17.       yield return www;
    18.  
    19.       if([url]www.error[/url] == null) {
    20.          // Now the texture is downloaded!
    21.          m_texture = [url]www.texture;[/url]
    22.       }
    23.  
    24.    }
    25.  
    26. }
    27.  

    This does also not work as StartCoroutine will return imediately after beeing called there by returning the value of the m_texture, which can be garantueed to be anything of the following: Null, the texture, garbage.

    This is not what i want i want a garantuee that it is the texture complete or null if download fails.

    So what todo? Well i could busy poll again :? so im back where i started.

    Anyone have a viable solution to this problem?
     
  2. Marc

    Marc

    Joined:
    Oct 4, 2007
    Posts:
    499
    Basically im looking for blocking download i think.
     
  3. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    I don't think stalling while waiting for loading from the web is a very good idea so i'll suggest something else.

    Make the texturefetcher have a exposed property called Texture that gives you the downloaded texture after the coroutine is finnished.

    I think most likely the problem is in how you actually use it. Because if the function that is doing something with the downloaded texture is not yielding then thats where the problem is.
     
  4. Marc

    Marc

    Joined:
    Oct 4, 2007
    Posts:
    499
    So you suggest doing something like:

    Code (csharp):
    1.  
    2. public class TextureFetcher : MonoBehavoir {
    3.  
    4.    private Texture m_texture = null;
    5.  
    6.    public Texture Texture {
    7.       get { return m_texture; }
    8.    }
    9.  
    10.    public IEnumerator GetTexture(string url) {
    11.      
    12.       WWW www = WWW(url);
    13.       yield return www;
    14.  
    15.       if([url]www.error[/url] == null) {
    16.           m_texture = [url]www.texture;[/url]
    17.       }
    18.  
    19.    }
    20.  
    21. }
    22.  
    23. public class SomeGameObject : MonoBehaviour {
    24.  
    25.     public string textureUrl;
    26.  
    27.     private TextureFetcher fetcher;
    28.  
    29.     void Start() {
    30.  
    31.         fetcher = new TextureFetcher();
    32.  
    33.         yield return StartCoroutine(fetcher.GetTexture(textureUrl));
    34.  
    35.         renderer.material.mainTexture = fetcher.Texture;
    36.  
    37.     }
    38. }
    39.  
    40.  
    I notice that in JavaScript Start can be a coroutine? what about in C#?
     
  5. Marc

    Marc

    Joined:
    Oct 4, 2007
    Posts:
    499
    Think i better outline the full purpose of this excercise, as i dont think its entirely clear what i am really trying to accomplish, so i drew a little schematic of the system.

    Idea is to have an updater gameobject which is responsible for tracking versions of textures for now. Attached to this updater object is a Storage object which is utilizing the new file:// and storing textures for offline usage using the WWW class and normal IO for write.

    Game objects can ask the updater if their texture should be updated given the game objects unique id or the textures. the updater will then check its internal knowlegde about updates or ask the server if there is an update for this id. If there is it will tell the object that there is an update and the object can ask for the new texture. which the updater will fetch from the server, store in the storage and return to the gameobject.

    A sort of Offline cache without time limits on data stored. Sorta snapshot like. The storage will facilitate offline use as all textures will be drawn from that incase there are no updates on startup of app or there is no connection to server.

    The storage component can be replaced to facilitate real caching on the client if need be.

    The TextureFetcher was my simplification of the Updater.

    I thought of doing it the other way round so objects where self updating but that could cause an insane amount of connections with associated overhead, so would prefer the more centralized version here as it also leaves most changes regarding the update system in one place.

    Dunno if that change anything but here it is. Am still looking into the idea from Joachim.

    Edit: This is ofc for standalone systems.

    Regards,
    Marc
     

    Attached Files:

  6. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    If you really want to do blocking and you are not going to use the web player you can always just do:

    while (!www.isDone)
    ;

    And then use no coroutines.
     
  7. Marc

    Marc

    Joined:
    Oct 4, 2007
    Posts:
    499
    Yeah but we alle know its not a very nice to busy poll, so was deperately hoping i could avoid it.

    Am i correct when i state that:

    1. The WWW class makes one (socket) connection per instance on standalone?

    2. That Start cannot be used as a Coroutine in C#?
     
  8. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Start can definately be used as a coroutine. It's actually the most common entry point for coroutines. (You need to make the return value be IEnumerator for any type of coroutine if you are using C#)

    In standalone it's one socket connection per download. When the download is complete, it's automatically cleaned up and only the downloaded data kept around until it is no longer referenced from C#.
     
  9. Marc

    Marc

    Joined:
    Oct 4, 2007
    Posts:
    499
    Thanks alot for clarifying Joachim! Your help is very much apreciated.

    Cheers,
    Marc
     
  10. Marc

    Marc

    Joined:
    Oct 4, 2007
    Posts:
    499
    Two more quick questions. Reference API says: http://unity3d.com/support/documentation/ScriptReference/WWW-isDone.html

    "If you try to access any data while isDone is false, the program will block while finishing the download."

    How is that to be understood? If i do:

    Code (csharp):
    1.  
    2. Texture tex = [url]www.texture;[/url]
    3.  
    or

    Code (csharp):
    1.  
    2. if ([url]www.error[/url] == null);
    3.  
    Will the program block while the data is not completely downloaded?

    Also if i do code like:

    Code (csharp):
    1.  
    2. while(![url]www.IsDone[/url]);
    3.  
    Will i end up in an infinite loop here if download fails due to loss of connection?