SchBoard 2 개발일지 - IV

Sch·2022년 8월 17일

SchBoard 2

목록 보기
4/16
post-thumbnail

아주 오랜만에 적는... SchBoard 2 개발일지이다. 한동안 개발을 안 하고 있었는데, SchBoard에 대한 막연한 막막함때문이기도 하고, 표면적으로는 대학교 1학년 생활에 적응하는 것이 좀 시간이 걸렸다고 하리라.

오늘은 오랜만에 코드를 보는 김에 코드를 읽어가며, 이전에 작성한 개발일지를 읽어가며 코드에 대한 리뷰(복습)를 진행했고, 그 김에 코드의 여러 부분에 주석을 달아두었다. 언제나 느끼는 것이지만, 코드를 쓸 때에는 주석이 아주 거추장스럽게 느껴지지만 언제나 주석은 필요한 것이라고 느낀다.

또한 오늘은 대망의 Component 계산 구현을 다뤘다. 이제 본격적으로 계산을 수행할 수 있는 로직 시뮬레이터가 된 것이다. 오늘 이 일지에서는 이 부분에 대해서 자세히 다루어보도록 하고싶다.

코드 리뷰

SchBoard 2의 기능 구현(js) 코드는 크게 4가지 파일로 나누어져 있다.

먼저, notification.js는 화면 중앙에 뜨는 알림 창을 관리한다. 추상화를 하거나 탭을 관리하거나 하는 등, 사용자에게 키보드로 입력받아야 하는 특수한 값이 있거나, 사용자에게 전달하여야 하는 정보가 있을 때에 유용하게 사용할 수 있을 것이다.

그리고 objects.jscanvas에 띄우는 요소들의 가장 기본적인 기능에 대해 다룬다. 즉, 사각형이나 글씨, 선을 띄우는 가장 기본적인 기능을 담당하는 "Object"들을 모아놓은 곳이다. 이 클래스에서 다루는 기능들은 실질적으로 SchBoard와는 관계가 없다고 판단된다.
오브젝트는 크게 ObjectCameraObject로 구분된다. 전자는 canvas의 좌표계에서 작동하는 선, 사각형, 원이고, 후자는 camera의 세계에서 작동하는 것들이다.

실제로 SchBoard 중심 기능의 구현은 components.js에서 주로 이루어진다. 이 프로젝트에서 "Component"란 SchBoard 위의 논리회로 상에서 하나의 계산을 맡아 하는 계산단위를 의미한다. 실제 브레드보드 컴퓨팅에서의 반도체에 해당하는 단위이다.
Component에는 Socket이 연결되어있고, Socket에는 Wire가 연결되어있다. Component에 연결된 SocketINPUT 소켓과 OUTPUT 소켓으로 나뉘는데, Component에서는 INPUT 소켓의 상태를 통해 OUTPUT 소켓의 상태를 계산한다. 이때 Socket에 연결된 WireOUTPUT 소켓의 상태 변경에 따라 다른 쪽에 연결된 INPUT 소켓을 업데이트하고, 이 소켓에 연결된 Component가 계산을 수행하도록 하는 등의 방식으로 연결된 모든 회로가 작동된다.

index.js는 코드의 가장 마지막에서, notification.js, objects.js, components.js를 유기적으로 연결하고, 이벤트를 받으며 메인 루프를 구성하는 역할을 한다.

계산

모든 계산은 Wire를 통해 SocketSocket이 연결되는 것으로 시작한다.

  1. 컴포넌트 A에 있는 출력타입 소켓 AO1과 컴포넌트 B에 있는 입력타입 소켓 BI1을 와이어 W1로 연결한다고 하자.
  2. 이때 W1 자체의 상태는 AO1의 값과 같은 값으로 설정된다. 이는 AO1이 A에 연결된 출력타입 소켓이기 때문이다.
  3. W는 AO1로부터 얻은 자신의 상태를 BI1로 전달한다.
  4. 이때 BI1은 상태변화와 함께 B를 componentCalculationQueue에 추가한다.
  5. 메인루프에서 큐의 컴포넌트들의 상태 계산을 envoke한다.
  6. B는 바뀐 BI1의 상태에 기반하여 다시 계산을 진행한다.
    • 이때 B의 출력타입 소켓 BO1의 상태가 변경되었고, 이 소켓은 와이어 W2를 통해 컴포넌트 C의 입력타입 소켓 CI1과 연결되어있다고 하자.
  7. BO1의 상태가 변경되면 W2는 이것을 감지하여 자신의 상태를 BO1과 같은 것으로 바꾸고 CI1의 상태를 업데이트한다. 즉, 1.에서 진행한 것과 같이 수행한다.

기존의 SchBoard에서 안타깝다고 생각한 점 중 하나는 회로가 커지면 실행되는 계산에 관계없이 언제나 부하가 커진다는 것이었다. 이 단점이 가장 크게 드러난 곳이 메모리를 다루는 때였는데, 나는 이것이 계산을 굳이 해야 하는 상황이 아님에도 불구하고 계산을 진행하는 R-S Latch가 있기 때문이라고 보았다. 따라서 업데이트되는 요소만을 모아 그와 연결된 요소만 계산을 진행하도록 획기적으로 연산의 수를 줄일 수 있을 것이라고 판단했다.

처음에는, Wire를 연결해 componentCalculationQueue에 요소가 들게 되면, while문을 사용해 큐에 더 이상 아무런 컴포넌트도 남지 않을 때까지 계산을 진행하려고 했다. 하지만 이렇게 하면 A := !A와 같은 클락을 만들었을 때에 무한정 while문에서 빠져나오지 못하는 일이 발생할 것이라고 예상했다. 따라서 calculationLimit 변수를 두어 한 프레임에서 계산할 수 있는 컴포넌트 개수를 정해두었다.

하지만 위에서 명시한 루프대로 계산을 수행하면 A := !!A와 같은 연산을 수행했을 때에 큐에 이 연산이 언제나 올라와있게 되는 문제가 발생한다는 것을 알았다. A := !!A는 안정적인 경우이기 때문에 컴포넌트 계산을 수행해도 소켓의 값이 변하지 않는다는 점을 이용해서, 소켓의 값이 변하지 않는다면 큐에 계산 요청을 올리지 않도록 하여 최적화를 진행했다.

Or, Not, True 컴포넌트

이렇게 계산을 구현하고 나서는 TrueComponent, NotComponent, OrComponent 순서대로 클래스를 통해 컴포넌트를 구현했다.

여기서 NOT, OR 요소를 가지고 드모르간 법칙을 구현하면 AND 연산을 구현할 수 있고, NOT, OR, AND의 세가지 요소가 있다면 부울대수를 통해 모든 컴퓨터적 계산을 수행할 수 있다. 즉, 최소한의 연산을 가지고 컴퓨터를 구현하겠다는 SchBoard 프로젝트의 목적을 구현할 수 있게 된 것이다.

여기까지만 구현하더라도 사실상 끝난 것이다. 주어진 컴포넌트들을 이용해서 모든 계산을 유한 시간 안에 구현할 수 있기 때문이다.
하지만 SchBoard는 이것만을 목적으로 가지지 않는다. 컴포넌트들을 모아 추상화를 진행하여 AND, NAND, XOR, NOR, NXOR을 구현하고, Full Adder를 구현하고, R-S Latch를 구현하고, 이것들을 합쳐서 가산기, 계산기를 만들어야 한다. 이 계산기를 신기하게 합쳐서 컴퓨터 속에서 동작하는 컴퓨터를 만들 것이다.

다음 개발일

다음 개발일에는 다음을 만들자.

  • 추상화
  • 화면에 컴포넌트 추가 구현
  • 화면에서 컴포넌트 제거 구현
  • 탭 구현
  • 런타임에 calculationLimit 수정 구현
  • 스페이스 드래깅을 통한 카메라 이동
profile
https://me.shtelo.org/

0개의 댓글