Webworker에서 JS 물리적 - 개념 증명 실행

시리즈: Javascript 물리적 세계 탐색


Web Workers는 기본 스레드에서 컴퓨팅 집약적 작업을 제거하는 데 유용한 방법입니다.나는 그것들을 사용하는 것에 흥미를 느낀 지 이미 오래되었지만, 내가 종사하는 프로젝트 중 그것들을 사용하는 것이 합리적이라는 것을 진정으로 증명하는 것은 하나도 없다.지금까지!이 짧은 시리즈에서, 나는 웹워크스, 물리학,pixi를 어떻게 사용하는지 탐색할 것이다.js와 다른 사람들이 인터랙티브 네트워크 체험과 게임을 만듭니다.
  • Live
  • Github

  • 네트워크 작업자 tldr;

  • 백그라운드 스레드에서 실행되는 스크립트
  • 메시지 전송 및 수신
  • 을 통해 주 스레드와 통신
    깊이 있는 정보는 내가 설명할 수 있는 것보다 낫다.
  • Using web workers for safe, concurrent JavaScript - Bruce Wilson, Logrocket
  • MDN entry
  • 왜?


    인터넷 종사자를 사용하는 장점은 부인할 수 없다.가장 중요한 것은 주 라인의 응답성을 유지하는 것이다.동결된 웹 페이지와 느린 사용자 인터페이스는 나쁜 사용자 체험을 가져올 수 있다.나의 예에서 물리 시뮬레이션 속도가 20-30fps로 낮아져도 메인 라인 렌더링기는 여전히 고정된 144fps로 운행한다.이것은 애니메이션을 아름답고 생동감 있게 유지하고 페이지가 사용자의 입력에 응답하는 데 도움이 된다.
    나는 과거의 CPU 집약형 지형 생성에 대해 죄책감을 느꼈다. 그것은 사용자의 브라우저를 2-3초 동안 동결시켰다. 이것은 매우 나쁘다.

    개념 증명 구현:


    이것은 순서에 따라 점진적인 강좌가 아니다. 나는 이 문장을 더욱 개념적이고 코드가 간결하게 하고 싶다.너는 the project repo에서 나의 스파게티 코드를 따를 수 있을 것이다.

    1. 위트 포장기


    나는 불필요한 복잡성을 피하기 위해 어떤 틀도 사용하지 않기로 결정했다.번들에 대해 Vite를 사용하기로 결정했습니다and the provided vanilla Typescript template.Typescript 파일에서 easy way to import webworkers 및 종속 항목까지 제공합니다.

    2.피시.js 렌더링


    Pixi.js는 상당히 사용하기 쉬운 WebGL 렌더링입니다.그것은 화면에 우리가 무엇을 하고 있는지 나타낼 것이다.내가 한 모든 것은 복제the examples 중 하나를 통해 복제할 수 있다.당신은 단지:
  • 렌더링 설정
  • 텍스쳐를 탑재하고 정령을 제작
  • 스피커의 정령 위치와 각도 업데이트
  • 3. 마지막으로 일꾼을 성공시키자!

  • 노동자와 함께 보관한다. 예를 들어physicsWorker.ts.코드는 작업 부하 시 실행됩니다.
  • 주 스레드의 보조 스레드 가져오기 및 초기화 - vite docs
  • 지금부터 탐지기를 설정하고 메인 라인과 작업 라인
  • 사이에서 메시지를 보낼 수 있습니다.

    4. 일꾼 중의 물리 엔진.


    Matter.js는 내가 사용하기로 결정한 2D 물리 엔진이다.그것은 성능이 가장 좋은 것은 아니지만 사용자에게 친절하고 코드의 복잡도를 낮추는 데 도움이 된다.
    엔진, 세계, 게임 순환은 웹 워크맨이 불러올 때 생성됩니다.Gameloop은 연속적으로 실행되고 호출되는 함수입니다. Engine.update(physics.engine, delta);

    5. 소통과 지휘 모드


    앞서 언급한 바와 같이 워크맨과 라인은 메시지를 통해 통신한다.나는 이것이 자연적으로 적합하다는 것을 발견했다. command pattern
    Actor(주 스레드 또는 작업 스레드)는 바디 작업을 수행하는 데 필요한 모든 정보를 포함하는 객체를 보냅니다.나는 아래와 같이 나의 명령을 조직하기로 결정했다.
    const command = {
      type: "ADD_BODY",
      data: {
        x: 0,
        y: 0,
        width: 10,
        height: 10,
        options: {
          restitution: 0,
        },
      },
    };
    
    상기 명령을 보내려면 주 스레드 호출worker.postMessage(command);.직원들이 그것을 받아들일 수 있도록, 우리는 탐지기를 설치해야 한다.
    // Worker has to call 'self' to send and receive
    self.addEventListener("message", (e) => {
      const message = e.data || e;
    
      // Worker receives a command to ADD_BODY
      if (message.type == "ADD_BODY") {
        // it does stuff
        const { x, y, width, height, options } = message.data;
        const body = physics.addBody(x, y, width, height, options);
    
        // Worker sends a command to main thread (BODY_CREATED)
        // it will be used to spawn a sprite
        self.postMessage({
          type: "BODY_CREATED",
          data: {
            id: body.id,
            x,
            y,
            width,
            height,
            angle: 0,
            sprite: undefined,
          },
        });
      }
    });
    
    다음은 이 예의 작업 원리의 개술이다

    6. 기능 설명


    신체를 창조하다

  • 주 스레드는 위치, 너비, 높이 및 physics options 명령을 발송ADD_BODY
  • 보조 라인이 ADD_BODY을 수신할 때 주어진 파라미터를 가진 주체를 세계에 추가
  • 주체를 추가하면 일꾼은 BODY_CREATED 명령을 주 라인으로 보냅니다.이 메시지의 가장 중요한 부분은 id입니다. 기술적으로 관련이 없는 자바스크립트 대상 (worker의 바디와main의sprite) 이 어떻게 동기화되는지입니다.또한 폭, 높이, 위치 및 각도
  • 를 보냅니다.
  • 메인 라인이 BODY_CREATED 위치를 수신할 때 수신된 데이터와 분배된 PIXI.Sprite 위치를 포함하는 대상을 만듭니다.
  • 물리적 엔진과 렌더기 간에 객체 위치 동기화

  • 각 프레임 물리 엔진은 명령BODY_SYNC을 보내는데 물리 세계의 모든 물체의 위치와 각도를 포함한다.이것은hashmap 형식으로 저장되며 주체 id는 키입니다.
  • const data: any = {};
    
    for (const body of world.bodies) {
      data[body] = {
        x: body.position.x,
        y: body.position.y,
        angle: body.angle,
      };
    }
    self.postMessage({
      type: "BODY_SYNC",
      data,
    });
    
  • 메인 나사 연결 밸브BODY_SYNC.이전에 추가한 모든 엔티티를 순환하고 업데이트합니다.
  • if (e.data.type == "BODY_SYNC") {
      const physData = e.data.data;
    
      bodySyncDelta = e.data.delta;
    
      for (const obj of physicsObjects) {
        const { x, y, angle } = physData[obj.id];
        if (!obj.sprite) return;
        obj.sprite.position.x = x;
        obj.sprite.position.y = y;
        obj.sprite.rotation = angle;
      }
    }
    

    정말 쓸모가 있다!



    다음과 같은 문제가 발생했습니다.

  • 물리적 성능은 부족하지만 아직도 개선해야 할 점이 많다.
  • 때때로 물체는 경계를 벗어나 10000+의 x, y 좌표로 끊임없이 날아들어 감속을 초래하여 결국 추락한다.나는 곧 이 문제를 해결하고 좌표가 3000이 넘는 물체를 동결시켰다. 이것은 완벽한 해결 방안이 아니며 앞으로도 주의해야 한다.
  • 간단한 명령 모드가 잘 작동하지만, 일부 용례에서는 매우 복잡해질 수 있습니다
  • 미래의 개선 고려


    1. 중요합니다.js가 느려요.


    this outdated benchmark 사건에 의하면.js는 사용 가능한 속도가 가장 느린javascript 물리 엔진 중의 하나입니다.그 이후로 성능이 향상되었지만 다른 선택이 있었다.저는 js가 연결된 WASM 쿠터에 관심이 없습니다. 예를 들어

  • box2dwasm - WASM으로 컴파일된 오래되고 유지보수된 C++ 라이브러리입니다.문서가 부족해서 개발자의 경험이 매우 나쁜 것 같다.

  • rapier.rs-현대물리도서관은 녹으로 쓴다.그것은 보기에도 좋고 성능도 좋아서 언뜻 보기에 개발자의 체험은box2d보다 훨씬 좋다.Documentation 희망을 주셨어요!
  • 일반적으로 WASM 엔진이 아닌 WASM 엔진을 선택하면 성능이 크게 향상될 것입니다.

    2. 인터넷 종사자 정보


    작업 라인과 주 라인 사이에 고주파(gameloop)로 메시지를 통해 대량의 데이터를 보내면 성능이 크게 떨어질 수 있다.
    이 문제를 깊이 있게 탐구한다: "Is postmessage slow?" - surma.dev
    고려 사항:
  • JSON.그런 다음 JSON을 문자열로 만듭니다.데이터 분석(내 용례상 성능이 향상되지 않은 것 같다)
  • 사용ArrayBuffer 및 근로자와 주요 근로자
  • 간 소유권 양도
  • 사용SharedArrayBuffer을 통해 소스 사이트의 소유권을 유지하고 두 스레드 모두 사용할 수 있음Atomics 데이터 액세스
  • 내 기준을 세울 때가 된 것 같아!

    3. 일반적인 실현이 아닌 웹 워크맨 라이브러리 사용


    나는 일반 인터넷 종사자와의 소통이 매우 복잡해질 것이라고 상상할 수 있다.Comlink 이미 내 명세서에 한동안 열거되어 있으니 한번 해 보고 싶다.
    출처:
    Comlink는 인터넷 종사자들을 즐겁게 한다.컴링크는 작은 도서관(1.1kB)으로 포스트 메시지에 대한 심리적 장애를 없애고 직원들과 협력하고 있다는 사실을 숨긴다.
    좀 더 추상적인 측면에서postMessage와 ES6 에이전트의 RPC 구현이다.

    Comlink Github 페이지 4. 렌더링 보간


    만약 용례가 더 필요하지 않다면, 나는 물리 엔진을 초당 30 또는 60 프레임에 잠글 수 있다.문제는 이 운동이 보기에 파도가 거세다는 것이다.
    나는 삽입값을 사용하여 사용 가능한 위치와 속도 데이터를 사용하여 대상의 운동을 예측하고 144fps에 달하는 프레임을 생성하여 매끄러운 애니메이션을 실현할 수 있다.

    끝났어.


    결과는 내가 예상한 것보다 훨씬 길었다.더 많아요?

    좋은 웹페이지 즐겨찾기