avatar
Published on

Rust로 web assembly로 game of life 만들어보기 (1)

Author
  • avatar
    Name
    yceffort

Table of Contents

Introduction

이 튜토리얼은 https://rustwasm.github.io/docs/book/game-of-life/introduction.html 에서 제공하는 Rust WebAssembly로 만드는 Game of Life 을 기반으로 작성되었습니다. 직접 튜토리얼을 따라하면서 단순히 번역 이외에도 최신 라이브러리 버전 기준으로 재작성하였으며, 설명이 부족하거나 생략된 부분에 대해서도 별도로 주석을 달았습니다.

기본적으로 러스트에 대한 완벽한 이해를 기반으로 하지 않고 작성되었기 때문에, 일부 러스트 문법에 대한 설명이 적혀있을 수도 있습니다.

Game of Life?

라이프 게임, 또는 생명 게임은 처음에 입력된 초기값을 기준으로 알아서 시작되는 게임이다.

이 게임은 무한한 개수의 사각형 (이하 세포)로 이루어진 격자위에서 실행된다. 각 세포 주위에는 8개의 이웃 세포가 있으며, 각 세포는 살아있거나 죽어있는 상태를 가진다. 그리고 이 세포의 다음 상태는 다음과 같이 결정된다.

  • 죽은 세포 이웃 중 딱 세개 가 살아 있으면 살아난다.
  • 살아 있는 세포 중 이웃이 두 개나 세개가 살아있으면 그 세포는 살아있고, 그외에는 죽는다.

1. Setup

시작에 앞서 설치해야 하는 기본 언어와 라이브러리는 다음과 같다.

  • rust
  • rustup
  • cargo
  • wasm-pack
  • cargo-generate: 이 라이브러리는 이미 존재하는 깃 저장소를 기본 템플릿으로 사용하여 빠르게 러스트 프로젝트를 만드는데 도움을 준다.
  • npm

2. Hello, World

프로젝트 클론

wasm-pack 을 기반으로 한 프로젝트를 빠르게 만들기 위해, 기본적으로 제공하는 프로젝트 템플릿이 존재하는데, 이를 기본으로 프로젝트를 만든다.

cargo generate --git https://github.com/rustwasm/wasm-pack-template

그리고 게임 이름을 입력한다. wasm-game-of-life

내부 살펴보기

.
├── Cargo.toml
├── LICENSE_APACHE
├── LICENSE_MIT
├── README.md
├── src
│   ├── lib.rs
│   └── utils.rs
└── tests
    └── web.rs

Cargo.toml

Cargo.toml은 이 패키지에서 필요로하는 의존성과, cargo metadata를 포함하고 있다.

src/lib.rs

mod utils;

use wasm_bindgen::prelude::*;

// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

#[wasm_bindgen]
extern {
    fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet() {
    alert("Hello, wasm-game-of-life!");
}

우리가 이제 만들려고 하는 webassembly의 루트 파일이다. wasm-bindgen을 사용하여 자바스크립트 인터페이스와 연결하는 것을 볼 수 있다. 이전에 예제에서 살펴본 것처럼, 이 경우에는 window.alert를 구현한 것으로 볼 수 있다.

src/utils.rs

pub fn set_panic_hook() {
    // When the `console_error_panic_hook` feature is enabled, we can call the
    // `set_panic_hook` function at least once during initialization, and then
    // we will get better error messages if our code ever panics.
    //
    // For more details see
    // https://github.com/rustwasm/console_error_panic_hook#readme
    #[cfg(feature = "console_error_panic_hook")]
    console_error_panic_hook::set_once();
}

작업을 좀더 용이하게 하기 위한 공통 유틸리티를 관리하는 파일이다. wasm 코드 디버깅 등 다양한 일을 할 수 있는데, 일단 이단계에서는 무시한다.

빌드

wasm-pack을 사용하여 빌드할 경우, 다음의 단계를 거친다.

  • rust 1.30 이상이 설치되어 있는지, 그리고 wasm32-unknown-unknown 타깃이 rustup을 통해 설치되어 있는지 확인
  • rust 소스를 webassembly .wasm 바이너리로 컴파일
  • wasm-bindgen을 사용하여 rust webassembly에서 사용할 수 있는 자바스크립트 api를 생성

wasm-pack build

빌드가 끝나면, pkg 디렉토리 아래에 다음과 같은 내용을 확인할 수 있을 것이다.

./pkg/
├── package.json
├── README.md
├── wasm_game_of_life_bg.js
├── wasm_game_of_life_bg.wasm
├── wasm_game_of_life_bg.wasm.d.ts
├── wasm_game_of_life.d.ts
└── wasm_game_of_life.js

pkg/wasm_game_of_life_bg.wasm

.wasm 파일은 러스트 컴파일러가 러스트 소스에서 생성한 WebAssembly 바이러니다. 여기에는 우리가 만든 러스트 함수와 데이터가 wasm 버전으로 컴파일 되어있다. 이 경우에는, greet()함수가 있을 것이다.

pkg/wasm_game_of_life.js

.jswasm-bindgen에 의해 생성되며, DOM 및 자바스크립트 함수를 rust로 import하고, WebAssembly 함수에 대한 api를 자바스크립트에 노출하기 위한 연결 고리를 제공한다. 방금 예제에서는, webassembly에서 보낸 greet 함수를 감싸는 javascript greet 함수가 존재한다. wasm과 javascript 간에 값을 주고받기 시작하면 이러한 경계를 넘어서는데 도움이 될 것이다.

pkg/wasm_game_of_life.d.ts

다들 아는 것처럼 d.ts는 타입스크립트 코드의 타입 추론을 돕는 파일이다. 만약 타입스크립트를 사용한다면, webassembly 함수를 Import 할 때 도움이 될 것이다. 타입스크립트를 사용하지 않는다면 무시해도 된다.

pkg/package.json

{
  "name": "wasm-game-of-life",
  "collaborators": ["GitHub <noreply@github.com>"],
  "version": "0.1.0",
  "files": [
    "wasm_game_of_life_bg.wasm",
    "wasm_game_of_life.js",
    "wasm_game_of_life_bg.js",
    "wasm_game_of_life.d.ts"
  ],
  "module": "wasm_game_of_life.js",
  "types": "wasm_game_of_life.d.ts",
  "sideEffects": false
}

package.json은 자바스크립트와 webassembly 패키지를 만드는데 필요한 메타데이터를 가진 파일이다. npm이 이 package.json을 사용하고, 자바스크립트 번들러는 이 패키지 내의 의존성, 버전 등을 관리할 수 있게 된다.

웹 페이지에서 보기

디렉토리에서, 아래 명령어를 실행하자.

npm init wasm-app www

@yceffort ➜ /workspaces/rust-playground/wasm-game-of-life (main ✗) $ npm init wasm-app www
npx: installed 1 in 3.952s
🦀 Rust + 🕸 Wasm = ❤

www 디렉토리 아래 npm package가 생성된 것을 볼 수 있다.

./www/
├── bootstrap.js
├── index.html
├── index.js
├── LICENSE-APACHE
├── LICENSE-MIT
├── package.json
├── package-lock.json
├── README.md
└── webpack.config.js

이 패키지에서, 우리가 사용할 webassembly를 사용할 수 있도록 dependencies에 의존성으로 걸어두어야 한다.

...
"dependencies": {
    "wasm-game-of-life": "file:../pkg"
  },

그리고 index.js를 아래 내용으로 바꾼다.

import * as wasm from 'wasm-game-of-life'

wasm.greet()

그리고 의존성을 설치한 뒤에, 실행해보면 alert가 정상적으로 뜨는 것을 확인할 수 있다.

wasm-alert

다음 포스트에서, 이제 구체적으로 game-of-life를 러스트를 통해서 구현해 보자.