Swift Firebase速度太慢,无法加载基于平铺的游戏

Swift Firebase速度太慢,无法加载基于平铺的游戏,swift,firebase,Swift,Firebase,我正在用swift和Firebase为iOS构建一个基于2d瓷砖的游戏。因为世界很大,所以我设计它,只订阅屏幕上的互动程序。也就是说,我没有为所有10000x1000个tile添加侦听器,而是将它们添加到屏幕上的tile中。当播放器移动时,我会注销旧的侦听器并注册新的侦听器。我在屏幕边缘添加了一个缓冲区,希望当它在屏幕上移动时,所有东西都能被充分加载。不幸的是,Firebase通常存在足够大的滞后,这一策略根本不起作用。在次优的互联网连接上,可以继续走进“未加载的世界”,有时需要几秒钟来加载丢失

我正在用swift和Firebase为iOS构建一个基于2d瓷砖的游戏。因为世界很大,所以我设计它,只订阅屏幕上的互动程序。也就是说,我没有为所有10000x1000个tile添加侦听器,而是将它们添加到屏幕上的tile中。当播放器移动时,我会注销旧的侦听器并注册新的侦听器。我在屏幕边缘添加了一个缓冲区,希望当它在屏幕上移动时,所有东西都能被充分加载。不幸的是,Firebase通常存在足够大的滞后,这一策略根本不起作用。在次优的互联网连接上,可以继续走进“未加载的世界”,有时需要几秒钟来加载丢失的磁贴

但问题是:在同一连接和同一设备上的其他MMO iOS游戏运行良好。这不是一个糟糕的连接。这让我怀疑我的实现,或者Firebase本身有错

从根本上说,我每次迈出一步都要等待“加载一次”事件大约20个瓷砖。一个步骤大约需要1/4秒,因此每秒钟我都要从Firebase请求大约100件物品。不过,我想不出更好的办法了。Firebase文档表明这不应该是一个问题,因为这都是一个套接字连接。我可以将对象“bucket”到10x10个块中,这意味着我可以订阅更少的对象,但这在总数据传输方面也会更加浪费。如果套接字连接得到了真正的优化,那么整个数据传输应该是唯一的瓶颈,这意味着这种策略是错误的

编辑 这里有一段视频展示了它是如何工作的。缓冲区大小已减小到
-1
,因此您可以轻松看到屏幕边缘以及加载和卸载磁贴。在接近视频的结尾时,时间滞后,我徘徊在空虚中。我打开另一个游戏,它几乎立即加载。(注意,我在再次加载屏幕之前就结束了录制。它从来都不会加载失败,所以代码并不是不起作用。这纯粹是延迟。)

下面是我用来加载瓷砖的代码。每个瓷砖都会调用一次。正如我所说的,这意味着这个代码每一步被并行调用大约20次。所有其他应用程序都以良好的速度运行,没有延迟。我在东京使用LTE连接的MiFi,所以它是一个可靠的连接

  /**
   * Given a path to a firebase object, get the snapshot with a timeout.
   */
  static func loadSnapshot(firebasePath: String!) -> Promise<FDataSnapshot?> {
    let (promise, fulfill, _) = Promise<FDataSnapshot?>.pendingPromise()
    let connection = Firebase(url: Config.firebaseUrl + firebasePath)
    connection.observeSingleEventOfType(.Value, withBlock: { snapshot in
      if !promise.resolved {
        fulfill(snapshot)
      }
    })
    after(Config.timeout).then { () -> Void in
      if !promise.resolved {
        DDLogWarn("[TIMEOUT] [FIREBASE-READ] \(firebasePath)")
        fulfill(nil)
        //reject(Errors.network)
      }
    }
    return promise
  }
/**
*给定firebase对象的路径,获取具有超时的快照。
*/
静态func loadSnapshot(firebasePath:String!)->Promise{
让(允诺,履行)允诺
let connection=Firebase(url:Config.firebaseUrl+firebasePath)
connection.observeSingleEventOfType(.Value,withBlock:{snapshot in
如果!答应。解决{
完成(快照)
}
})
在(Config.timeout)之后。然后{()->Void in
如果!答应。解决{
DDLogWarn(“[TIMEOUT][FIREBASE-READ]\(firebasePath)”)
完成(无)
//拒绝(错误。网络)
}
}
回报承诺
}
磁贴位于
[ROOT]/tiles/[X]X[Y]
。大多数磁贴包含很少的数据,但如果该磁贴上有对象(即其他玩家),则存储这些对象。以下是Firebase的屏幕截图:

编辑2 根据请求,我非常简单地重新创建了这个问题。下面是一个100行的
XCTestCase
类:

用法:

  • 将文件放入您的Swift项目(它应该是独立的,只需要Firebase)
  • firebaseUrl
    的值更改为您的根URL(即
    https://MyProject.firebaseio.com
  • 运行一次
    testSetupDatabase()
    函数测试以设置数据库的初始状态
  • 运行
    testWalking()
    函数测试滞后。这是主要的测试。如果加载任何磁贴的时间超过2秒,则它将失败
  • 我在几个不同的连接上试过这个测试。一流的办公室连接顺利通过,但即使是高端LTE或MiFi连接也会失败
    2秒
    已经是一个很长的超时,因为它意味着我需要一个
    10个tile
    缓冲区(0.2秒*10个tiles=2秒)。下面是我连接到LTE连接时的一些输出,显示加载互动程序花费了近10秒(!!):
    错误:-[ForeverMazeTests.LagTests testWalking]:Xctasertrue失败-Tile 2x20使用了9.50058007240295
    我运行了一些测试,当我通过3G连接进行测试时,加载在15-20秒内完成。在我的常规连接中,这需要1-2秒,因此差异可能完全取决于带宽

    我将您的测试用例重写为JavaScript版本,因为我很难弄清楚到底发生了什么。在这里找到我的:

    您可能会注意到,每个步骤所花费的时间加起来并不等于所有步骤所花费的时间。基本上,当管道中仍有请求时,启动每个请求。这是加载这些项的最有效方法,但这确实意味着您需要以不同的方式度量性能

    基本上所有步骤几乎同时开始。然后等待第一个响应(在上面的例子中包括建立从客户端到正确Firebase服务器的WebSocket连接),然后响应以合理的间隔出现(假设每个步骤有20个请求)

    所有这些都很有趣,但它当然不能解决你的问题。我建议您将数据建模到屏幕大小的存储桶中。因此,不要将每一块瓷砖单独存放,而是将每10x10块瓷砖存放在一个“桶”中。您将减少每个单独请求的开销,并且每10个步骤最多只需要请求一个bucket

    更新 我很确定我们只是在调试您的基准测试方法的多个工件。如果我更新了c
    var ref = new Firebase(URL);
    var tilesPerStep = 20;
    var stepsToTake = 100;
    
    function testWalking() {
      var startTime = Date.now();
      var promises = [];
      for (var x=0; x < stepsToTake; x++) {
        promises.push(testStep(x));
      }
      Promise.all(promises).then(function() {
        console.log('All '+promises.length+' steps done in '+(Date.now()-startTime)+'ms');
      });
    }
    
    function testStep(x) {
      var result = new Promise(function(resolve, reject){
        var tiles = ref.child("/tiles_test");
        var loading = 0;
        var startTime = Date.now();
        console.log('Start loading step '+x);
    
        for (var y=0; y < tilesPerStep; y++) {
          loading ++;
          tiles.child(x+'x'+y).once('value', function(snapshot) {
            var time = Date.now() - startTime;
            loading--;
            if (loading === 0) {
              console.log('Step '+x+' took '+(Date.now()-startTime)+'ms');
              resolve(Date.now() - startTime);
            }
          });
        }
      });
      return result;
    }
    
    testWalking();
    
    "Start loading step 0"
    "Start loading step 1"
    "Start loading step 2"
    "Start loading step 3"
    "Start loading step 4"
    "Start loading step 5"
    "Start loading step 6"
    "Start loading step 7"
    "Start loading step 8"
    "Start loading step 9"
    "Step 0 took 7930ms"
    "Step 1 took 7929ms"
    "Step 2 took 7948ms"
    "Step 3 took 8594ms"
    "Step 4 took 8669ms"
    "Step 5 took 9141ms"
    "Step 6 took 9851ms"
    "Step 7 took 10365ms"
    "Step 8 took 10425ms"
    "Step 9 took 11520ms"
    "All 10 steps done in 11579ms"
    
    func testWalking() {
        let expectation = expectationWithDescription("Load tiles")
        let maxTime = self.timeLimit + self.stepTime * Double(stepsToTake)
    
        let startTime = NSDate().timeIntervalSince1970
    
        for (var x=0; x<stepsToTake; x++) {
            let delay = Double(x) * stepTime
            let data = ["x":x, "ex": expectation]
            stepsRemaining++
            NSTimer.scheduledTimerWithTimeInterval(0, target: self, selector: Selector("testStep:"), userInfo: data, repeats: false)
        }
        waitForExpectationsWithTimeout(maxTime) { error in
            let time = NSDate().timeIntervalSince1970 - startTime
            print("Completed loading after \(time)")
            if error != nil {
                print("Error: \(error!.localizedDescription)")
            }
        }
    }
    
    /**
    * Helper function to test a single step (executes `tilesPerStep` number of tile loads)
    */
    func testStep(timer : NSTimer) {
        let tiles = Firebase(url: firebaseUrl).childByAppendingPath("/tiles_test")
        let data = timer.userInfo as! Dictionary<String, AnyObject>
        let x = data["x"] as! Int
        let expectation = data["ex"] as! XCTestExpectation
        var loading = 0
        print("Start loading \(x)")
    
        for (var y=0; y<tilesPerStep; y++) {
            loading++
            tiles.childByAppendingPath("\(x)x\(y)").observeSingleEventOfType(.Value, withBlock: { snapshot in
                loading--
                if loading == 0 {
                    print("Done loading \(x)")
                    self.stepsRemaining--
                    if self.stepsRemaining == 0 {
                        expectation.fulfill()
                    }
                }
            })
        }
    }