如何阻止 clojurescript 应用程序在运行时阻止浏览器事件?

问题描述 投票:0回答:1

背景

作为参考,我正在构建一个小型 ECS 游戏引擎:

  • clojure
  • Threejs 作为图形目标之一
  • shadow-cljs 用于构建

示例的完整代码可以在这里找到。

问题

当我在浏览器中加载应用程序时,我的游戏引擎的主“游戏循环”会阻止 Threejs 使用

requestAnimationFrame
渲染到屏幕,

停止处理按键,直到游戏循环结束,此时两个功能都将停止恢复正常。

因此,典型的运行将如下所示:
  1. 刷新浏览器窗口
  2. 我可以看到主游戏循环在控制台中进行
  3. 在主游戏循环期间,屏幕上不会显示任何内容,也不会记录任何按键操作。
  4. 游戏结束后,按键事件全部“赶上”,并立即记录。
  5. 最后,Threejs 渲染器进行渲染。

这个问题我真是摸不着头脑。我试过:
  • go
  • 形式运行游戏,没有效果。
  • 首先运行 
    :start-up
  • 阶段,然后在 setTimeout 中运行游戏循环的其余部分...
  • 不从 
    run
     调用 
    init
  • fn 并尝试指定它应该在从 配置文件。

我希望在游戏循环期间按下按键事件后立即处理它们,并且我希望 Threejs 渲染器在每次在游戏循环中调用其函数时进行渲染(发生在 
draw-scene!

系统中)在提供的代码中)。

任何指点将不胜感激。

代码

src/app.cljs

(ns app
  (:require ["three" :as three]
            [snake :refer [add-snake-plugin]]
            [chaos.plugins.core :refer [add-core-plugins]]
            [chaos.plugins.timer :as timer]
            [chaos.engine.world :as chaos :refer [create-world
                                                  defsys
                                                  add-system
                                                  add-system-dependency
                                                  add-stage-dependency]]))

(defsys setup-threejs {}
  (let [w (.-innerWidth js/window)
        h (.-innerHeight js/window)
        aspect (/ w h)
        camera (three/PerspectiveCamera. 75 aspect 0.1 1000)
        renderer (three/WebGLRenderer.)]

    ;; Setup renderer and dom elements.
    (.setSize renderer w h)
    (.. js/document -body (appendChild (.-domElement renderer)))
    ;; Move camera back
    (set! (.. camera -position -z) 5)
    (println "Cam Z:" (.. camera -position -z))
    (println renderer)

    [[:add [:resources :camera] camera]
     [:add [:resources :scene] (three/Scene.)]
     [:add [:resources :renderer] renderer]]))

(defsys add-cube {:resources [:scene]}
  (let [scene (:scene resources)
        geometry (three/BoxGeometry. 1 1 1)
        material (three/MeshBasicMaterial. #js {:color 0x00ff00})
        cube (three/Mesh. geometry material)]
    (.add scene cube)
    []))

(defsys draw-scene! {:resources [:renderer :scene :camera]
                     :events :tick}
  (println "Drawing...")
  (let [{:keys [:renderer :scene :camera]} resources
        render-scene #(.render renderer scene camera)]
    (.. js/window (requestAnimationFrame render-scene))
    []))

(defsys capture-key-down {}
  (let [raw (atom [])
        add-event (fn [event]
                    (println "Keydown event!")
                    (swap! raw conj event))]
    (.addEventListener js/window "keydown" add-event)
    [[:add [:resources :key-down-events] raw]]))

(defsys handle-key-down {:resources [:key-down-events]}
  (println "KEYS" (:key-down-events resources)))

;; ... Pruned some irrelevant systems ...

(defn ^:dev/after-load run []
  (-> (create-world)
      add-core-plugins ;; Main engine plugins (removing has no effect)
      add-snake-plugin ;; The snake game library from another example (as above)
      (add-system :start-up setup-threejs) 
      (add-system :start-up add-cube)
      (add-system-dependency add-cube setup-threejs)
      (add-system :render draw-scene!)

      ;; Set of irrelevant systems which essentially just exit the game after 5 seconds.
      (add-system :start-up add-exit-timer)
      (add-system :pre-step pass-time)
      (add-system exit-after-5)

      ;; Gets key events from window
      (add-system :start-up capture-key-down)

      (add-stage-dependency :render :update)
      chaos/play))

;; shadow-cljs entry point
(defn init []
  (println "Refresh.")
  (run))

shadow-cljs.edn

;; shadow-cljs configuration
{:deps true
 :dev-http {8080 "public"}
 :builds
 {:app {:target :browser
        :modules {:main {:init-fn app/init}}}}}

three.js game-engine clojurescript shadow-cljs
1个回答
0
投票

浏览器中的 JavaScript 是单线程但异步的。因此,当运行一段未用 
async
 或 Promise(或 CLJS 时为 
core.async)分解的代码时,它将阻止 除了 Web Worker 之外的所有其他内容。

你的引擎似乎没有使用网络工作者,很可能它甚至不会从它们中受益,所以一个简单的

loop
将阻止一切,直到它退出。

解决方案是使用

requestAnimationFrame
安排循环迭代,其中每次迭代都会继续安排下一次迭代,直到遇到停止条件。因此,在这种情况下,你根本不能使用
loop
- 无论你在哪里使用
loop
,因为它仍然会阻塞整个主线程,因为没有任何其他线程。

它的外观示例:

(defn run-world [state]
  (let [next-state (step state)]
    (when-not (stop? next-state)
      (js/requestAnimationFrame #(run-world next-state)))))

请注意,此函数也不返回任何内容。但你可以让它返回一个在循环结束时获取值的

core.async
通道,或者最终得到解决的 Promise。

© www.soinside.com 2019 - 2024. All rights reserved.