Search Unity

WebGL woes using UnityWebRequest, slow, often doesn't work, uncertain why ?

Discussion in 'Web' started by JMousey, May 21, 2018.

  1. JMousey

    JMousey

    Joined:
    Dec 20, 2013
    Posts:
    12
    Greetings All,

    I am trying to make a simple app which downloads mesh data and renders it. My meshes are stored in a mysql data base as vertex, normal, and color buffer medium blobs. They can get quite large since they are science based visualizations. Some meshes contain up to 400k vertices. In Unity, I have a few scripts which creates a chunk object to initiate a download through the UnityWebRequest api. The request calls to a php script which grabs it from mysql.

    As a testbed, I have a C++ native app with my own mesh renderer that does the same thing through a different path: it talks to the same mysql database over mysql-connector (sockets?) and is threaded. Looking at the performance monitoring with this app, I see the database is capable of pushing 200-400 MB/s.

    However, when I use Unity's UnityWebRequest and go through php, it tanks to about 10 - 40 kb/s if it completes at all in webGl. The editor is much faster but no where near 1MB/s. I am not really a server or web tech engineer, but I am trying to get through this passing of data. When I hit the same php script in my browser the download speeds are much quicker. So I am not sure this is an isolated issue with php.

    I have read everything from all over the map, from rate limit mods in apache, to disabling gzip in .htaccess files, to an various number of CORS header strings, to patch working weird javascript node servers, to crazy cryptic thrid party plug ins.

    I have gotten as far as I could without having to ask directly now, what is the best strategy to take here ? Does anyone have a concrete example? The problem is straightly defined, get a large byte array from mysql to unity mesh inside Webgl.

    I have noticed a few things in my attempts at solving this.

    1. In the unity editor it runs fine, and gets all meshes (3GB) worth over the course of about 10 minutes.

    2. When I build in WebGL, the UnityWebRequests' arbitrarily stop, don't start, or don't finish. The web console shows a few of the requests, but sometimes they don't. They complete fine only sometimes. Other times its a mystery. There is nothing in the apache logs for hints, and no clear 'why' in any console log.

    A typical header response:
    3. The php, webGlbuild/index.html and mysql database are all on the same machine. I would expect 1GB/s through put and all url's are using intranet ip in connection strings.

    4. I get the same behavior in firefox or chrome: The network tab shows 30s for 4kb !!!!
    seen in [ https://ibb.co/dj7vwT ]

    My Scripts:

    Chunk Manager:
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEngine.Networking;
    4. using System;
    5. using System.IO;
    6. using System.Collections;
    7. using System.Collections.Generic;
    8.  
    9. public class ChunkVolume : MonoBehaviour
    10. {
    11.     public static ChunkVolume Instance = null;
    12.  
    13.     public Transform RootContainer;
    14.  
    15.     public GameObject ChunkedMeshPrefab;
    16.  
    17.     public static Material VertexColoringMaterial;
    18.  
    19.     public Dictionary<uint, ChunkedMesh> Chunks = new Dictionary<uint, ChunkedMesh>();
    20.     public uint MapID = 3;
    21.  
    22.     public int MaxDownloadThreads = 1;
    23.  
    24.     public float UpdateFrequency = 0.1f;
    25.  
    26.     private float SweepTimer;
    27.  
    28.     //**************************************************************************
    29.     // Initialization
    30.     //**************************************************************************
    31.     void Awake()
    32.     {
    33.         if( Instance == null )
    34.         { Instance = this; }
    35.         else
    36.         { Destroy(gameObject); }
    37.     }
    38.  
    39.     void Start()
    40.     {
    41.         Debug.Log("ChunkVolume::Start");
    42.         StartCoroutine(DownloadChunkList());
    43.         SweepTimer = UpdateFrequency;
    44.     }
    45.  
    46.     //**************************************************************************
    47.     // Returns the number of chunks actively downloading
    48.     //**************************************************************************
    49.     public int GetActiveDownloadCount()
    50.     {
    51.         int count = 0;
    52.         foreach ( ChunkedMesh chunkedMesh in Chunks.Values )
    53.         {
    54.             if( chunkedMesh.IsDownloading )
    55.             { count++; }
    56.         }
    57.      
    58.         return count;
    59.     }
    60.  
    61.     //**************************************************************************
    62.     // Gets the chunk id listing saved for a map on the server
    63.     //**************************************************************************
    64.     IEnumerator DownloadChunkList()
    65.     {
    66.         Debug.Log("Downloading Chunk List");
    67.         UnityWebRequest www = UnityWebRequest.Get("http://192.168.1.16/GetChunkList.php?MapID="+MapID+"");
    68.         www.SetRequestHeader("Cache-Control", "max-age=0, no-cache, no-store");
    69.         www.SetRequestHeader("Pragma", "no-cache");    
    70.         www.chunkedTransfer = false;
    71.         yield return www.SendWebRequest();
    72.  
    73.         Debug.Log("Download chunk list finished");
    74.         if (www.isNetworkError || www.isHttpError)
    75.         {
    76.             Debug.Log(www.error);
    77.         }
    78.         else
    79.         {
    80.             byte [] serverResponse = www.downloadHandler.data;
    81.             Debug.Log("Server chunk list response size " + serverResponse.Length );
    82.             if( serverResponse.Length > 0 )
    83.             {
    84.                 for( int i = 0; i<serverResponse.Length; i+=4 )
    85.                 {
    86.                     byte [] values = { serverResponse[i], serverResponse[i+1], serverResponse[i+2], serverResponse[i+3]};
    87.                     uint chunkID = BitConverter.ToUInt32( values, 0 );
    88.  
    89.                     GameObject newMeshedChunk = GameObject.Instantiate(ChunkedMeshPrefab, Vector3.zero, Quaternion.identity);
    90.                     newMeshedChunk.transform.parent = RootContainer;
    91.                     ChunkedMesh chunkedMesh = newMeshedChunk.GetComponent<ChunkedMesh>();
    92.  
    93.                     if( chunkedMesh != null )
    94.                     {
    95.                         chunkedMesh.name = chunkID.ToString();
    96.                         chunkedMesh.ChunkID = chunkID;
    97.                         chunkedMesh.MapID = MapID;
    98.                         Chunks.Add( chunkID, chunkedMesh );
    99.                     }
    100.                 }
    101.             }
    102.         }
    103.     }
    104.  
    105.     //**************************************************************************
    106.     // Sweeps for chunks that can still download and starts downloading
    107.     //**************************************************************************
    108.     private void SweepForChunks()
    109.     {
    110.         int downloadingCount = GetActiveDownloadCount();
    111.         if( downloadingCount < MaxDownloadThreads )
    112.         {
    113.             List<ChunkedMesh> chunksToRemove = new List<ChunkedMesh>();
    114.  
    115.             foreach ( ChunkedMesh chunkedMesh in Chunks.Values )
    116.             {
    117.                 if( !chunkedMesh.IsDownloaded && !chunkedMesh.IsDownloading )
    118.                 {
    119.                     chunkedMesh.StartDownload();
    120.                     downloadingCount++;
    121.                     if( downloadingCount >= MaxDownloadThreads )
    122.                     { break; }
    123.                 }
    124.                 else if ( chunkedMesh.IsDownloaded )
    125.                 {
    126.                     chunksToRemove.Add(chunkedMesh);
    127.                 }
    128.             }
    129.  
    130.             foreach( ChunkedMesh toRemove in chunksToRemove )
    131.             {
    132.                 Chunks.Remove( toRemove.ChunkID );
    133.             }
    134.         }
    135.     }
    136.  
    137.     //**************************************************************************
    138.     // Update is called once per frame
    139.     //**************************************************************************
    140.     void Update()
    141.     {
    142.         if( SweepTimer > 0 )
    143.         {
    144.             SweepTimer -= Time.deltaTime;
    145.             if( SweepTimer < 0 )
    146.             {
    147.                 SweepForChunks();
    148.                 SweepTimer = UpdateFrequency;
    149.             }
    150.         }
    151.     }
    152. }
    153.  
    The ChunkedMesh class:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Networking;
    3. using System;
    4. using System.IO;
    5. using System.Collections;
    6.  
    7. public class ChunkedMesh : MonoBehaviour
    8. {
    9.     public uint ChunkID = 0;
    10.     public uint MapID = 0;
    11.     public bool IsDownloading = false;
    12.     public bool IsDownloaded = false;
    13.  
    14.     //**************************************************************************
    15.     // Starts the coroutine download
    16.     //**************************************************************************
    17.     public void StartDownload()
    18.     {
    19.         Debug.Log("Starting download for " + ChunkID );
    20.         IsDownloading = true;
    21.         StartCoroutine(DownloadMesh());
    22.     }
    23.      //**************************************************************************
    24.     // Downloads binary mesh data from server, creates mesh on completion
    25.     //**************************************************************************
    26.     IEnumerator DownloadMesh()
    27.     {
    28.         UnityWebRequest www = UnityWebRequest.Get("http://192.168.1.16/GetMesh.php?ChunkID="+ChunkID+"&MapID="+MapID+"&LevelOfDetail=1");
    29.         www.SetRequestHeader("Cache-Control", "max-age=0, no-cache, no-store");
    30.         www.SetRequestHeader("Pragma", "no-cache");
    31.         www.chunkedTransfer = false;
    32.         yield return www.SendWebRequest();
    33.         if(www.isNetworkError || www.isHttpError)
    34.         {
    35.             Debug.Log(www.error);
    36.          
    37.         }
    38.         else
    39.         {
    40.             //Retrieve the Meshdata
    41.             byte[] results = www.downloadHandler.data;
    42.             if( results.Length > 4 )
    43.             {
    44.                 Debug.Log("MeshData length = " + results.Length );
    45.  
    46.                 //First 4 bytes are a packed 32 bit uint of vertex count
    47.                 BinaryReader meshBuffer = new BinaryReader(new MemoryStream(results));
    48.                 int numVertices = meshBuffer.ReadInt32();
    49.                 Debug.Log("Number of Vertices: " + numVertices );
    50.  
    51.                 Vector3 [] allPositions = new Vector3[numVertices];
    52.                 Color [] allColors = new Color[numVertices];
    53.                 Vector3 [] allNormals = new Vector3[numVertices];
    54.  
    55.                 //Vertex Positions
    56.                 for( int i = 0; i<numVertices; i++ )
    57.                 {
    58.                     allPositions[i].x = meshBuffer.ReadSingle();
    59.                     allPositions[i].y = meshBuffer.ReadSingle();
    60.                     allPositions[i].z = meshBuffer.ReadSingle();
    61.                 }
    62.  
    63.                 //Vertex Colors
    64.                 for( int i = 0; i<numVertices; i++ )
    65.                 {
    66.                     allColors[i].r =  (float)((float)meshBuffer.ReadByte()/255.0f);
    67.                     allColors[i].g =  (float)((float)meshBuffer.ReadByte()/255.0f);
    68.                     allColors[i].b =  (float)((float)meshBuffer.ReadByte()/255.0f);
    69.                     allColors[i].a = 1;
    70.                 }
    71.  
    72.                 //Vertex Normals
    73.                 for( int i = 0; i<numVertices; i++ )
    74.                 {
    75.                     allNormals[i].x = meshBuffer.ReadSingle();
    76.                     allNormals[i].y = meshBuffer.ReadSingle();
    77.                     allNormals[i].z = meshBuffer.ReadSingle();
    78.                 }
    79.  
    80.                 meshBuffer.Close();
    81.  
    82.                 int verticesLeft = numVertices-3;
    83.                 int passes = 0;
    84.                 int maxVertexCount = 65535;
    85.  
    86.                 while( verticesLeft > 0 )
    87.                 {
    88.                     int verticesToCreate = verticesLeft;
    89.                     if( verticesToCreate > maxVertexCount )
    90.                     { verticesToCreate = maxVertexCount; }
    91.  
    92.                     //Create mesh
    93.                     Mesh downloadedMesh = new Mesh();
    94.  
    95.                     int [] indices = new int[verticesToCreate];
    96.                     Vector3 [] positions = new Vector3[verticesToCreate];
    97.                     Color [] colors = new Color[verticesToCreate];
    98.                     Vector3 [] normals = new Vector3[verticesToCreate];
    99.  
    100.                     //Vertex Positions
    101.                     for( int i = 0; i<verticesToCreate; i++ )
    102.                     {
    103.                         positions[i] = allPositions[i + passes*maxVertexCount];
    104.                         colors[i] = allColors[i + passes*maxVertexCount];
    105.                         normals[i] = allNormals[i + passes*maxVertexCount];
    106.                         indices[i] = i;                    
    107.                     }
    108.  
    109.                     downloadedMesh.vertices = positions;
    110.                     downloadedMesh.normals = normals;
    111.                     downloadedMesh.colors = colors;
    112.                     downloadedMesh.triangles = indices;
    113.  
    114.                     if( passes == 0 )
    115.                     { GetComponent<MeshFilter>().mesh = downloadedMesh; }
    116.                     else
    117.                     {
    118.                         GameObject newMeshedChunk = GameObject.Instantiate(ChunkVolume.Instance.ChunkedMeshPrefab, Vector3.zero, Quaternion.identity);
    119.                         ChunkedMesh chunkedMesh = newMeshedChunk.GetComponent<ChunkedMesh>();
    120.  
    121.                         chunkedMesh.name = ChunkID.ToString();
    122.                         chunkedMesh.GetComponent<MeshFilter>().mesh = downloadedMesh;
    123.                         chunkedMesh.IsDownloaded = true;
    124.                         chunkedMesh.IsDownloading = false;
    125.                         chunkedMesh.ChunkID = ChunkID;
    126.                         chunkedMesh.MapID = MapID;
    127.  
    128.                         newMeshedChunk.transform.parent = ChunkVolume.Instance.RootContainer;
    129.                     }
    130.  
    131.                     verticesLeft -= verticesToCreate;
    132.                     passes++;
    133.                 }
    134.  
    135.  
    136.                 meshBuffer.Close();
    137.             }
    138.         }
    139.  
    140.         IsDownloading = false;
    141.         IsDownloaded = true;
    142.     }
    143. }
    144.  
    My PHP script:

    Code (CSharp):
    1. <?php
    2. include("GlobalEnum.php");
    3.  
    4. $ChunkID = $_REQUEST['ChunkID'];
    5. $MapID = $_REQUEST['MapID'];
    6. $LevelOfDetail = $_REQUEST['LevelOfDetail'];
    7.  
    8. $Query   = "SELECT VertexBuffer, ColorsBuffer, NormalsBuffer, VertexCount FROM Meshes WHERE MapID='$MapID' AND ChunkID='$ChunkID' AND LevelOfDetail='$LevelOfDetail'";
    9. $Result = mysqli_query($DataBaseConnection, $Query) or die('Error querying database.');
    10.  
    11. if ( !is_null($Result) )
    12. {
    13.     if ( $MeshData = mysqli_fetch_array($Result, MYSQLI_BOTH) )
    14.     {
    15.         $vertexSize = strlen($MeshData['VertexBuffer']);
    16.         $colorsSize = strlen($MeshData['ColorsBuffer']);
    17.         $normalsSize = strlen($MeshData['NormalsBuffer']);
    18.  
    19.         $totalSize = $vertexSize + $colorsSize + $normalsSize + 4;
    20.  
    21.         header("Access-Control-Allow-Credentials: true");
    22.         header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
    23.         header('Access-Control-Allow-Headers: Accept, X-Access-Token, X-Application-Name, X-Request-Sent-Time');
    24.         header("Access-Control-Allow-Origin: *");
    25.         header("Content-type: application/octet-stream");
    26.         header("Content-Transfer-Encoding: binary");
    27.         header("Pragma: no-cache");
    28.         header("Expires: 0");
    29.         header("Content-Length: $totalSize");
    30.  
    31.  
    32.         echo pack('L', $MeshData['VertexCount']);
    33.         echo $MeshData['VertexBuffer'];
    34.         echo $MeshData['ColorsBuffer'];
    35.         echo $MeshData['NormalsBuffer'];
    36.     }
    37. }
    38.  
    39. mysqli_free_result($Result);
    40. mysqli_close($DataBaseConnection);
    41.  
    42. ?>
    Hoping to get some more hints or advice.
     
    Last edited: May 21, 2018