HQService / HQFramework

A next-generation Bukkit development framework.
https://hqservice.kr
GNU General Public License v3.0
47 stars 1 forks source link

feat/view -> main: MVVM 스타일의 view, view model 및 element 추가 #19

Closed vjh0107 closed 1 year ago

vjh0107 commented 1 year ago

MVVM 스타일의 View, ViewModel 및 element 를 추가하였습니다. 주요 기능은 아래와 같습니다.

  1. state가 변경될 시 element가 같이 변경됩니다.
  2. view 와 view model 의 Coroutine 생명주기가 자동으로 관리됩니다.
  3. DSL 방식으로 간편한 gui 생성이 가능합니다.

아래는 예제입니다.

@Named("example")
@Bean
@HQFactory
class ExampleView : HQView(54, "제목") {
    private val viewModel by viewModels<ExampleViewModel>()

    override suspend fun CreateScope.onCreate() {
        launch {
            while (this@ExampleView.isActive) {
                viewModel.openedSeconds.set(viewModel.openedSeconds.get() + 1)
                bukkitDelay(20)
            }
        }

        launch {
            bukkitDelay(60)
            button(Material.STONE, 5) {
                item {
                    displayName("클릭한 횟수: ${viewModel.clickedTimes.get()}")
                    addLore("창을 열고 있는 시간: ${viewModel.openedSeconds.get()}")
                    addLore("버튼이 생성된 후로부터의 시간: ${viewModel.buttonCreatedSeconds.get()}")
                }
                subscribe(viewModel.clickedTimes, viewModel.openedSeconds, viewModel.buttonCreatedSeconds)
                onClick(viewModel::onInteractStone)
                onRender(viewModel::onRenderStone)
            }
        }
    }

    override suspend fun RenderScope.onRender(viewer: Player) {
        title("(제목) 창을 열고 있는 시간: ${viewModel.openedSeconds.get()}") {
            subscribe(viewModel.openedSeconds)
        }
    }
}
@Bean
@HQFactory
class ExampleViewModel : HQViewModel() {
    val clickedTimes = state(0)
    val openedSeconds = state(0)
    val buttonCreatedSeconds = state(0)

    fun onInteractStone(event: ButtonInteractEvent) {
        clickedTimes.set(clickedTimes.get() + 1)
    }

    fun onRenderStone(event: ButtonRenderEvent) {
        launch {
            while (this@ExampleViewModel.isActive) {
                buttonCreatedSeconds.set(buttonCreatedSeconds.get() + 1)
                bukkitDelay(20)
            }
        }
    }
}

onCreate 메소드는 플레이어에게 view를 열기 전 호출됩니다. onRender 메소드는 플레이어에게 view를 연 후 호출됩니다.

onCreate과 onRender 에서는 Button 을 배치할 수 있으며, onRender 에서는 title 을 변경할 수 있습니다. (nms 모듈 의존)

viewModels() 메소드를 통해 viewModel 을 주입받을 수 있습니다. viewModel 의 CoroutineScope 는 View가 close 될 때 view 의 CoroutineScope 가 종료되기 전 종료된 후 이어서 view 의 CoroutineScope 가 종료됩니다.

button 및 title 은 view 의 엘리먼트라고 표현되며, 엘리먼트들은 state 들을 subscribe 할 수 있습니다. subscribe 된 state 들이 변경될 때, 엘리먼트가 다시 그려지게 됩니다. 엘리먼트는 여러 state 들을 subscribe 할 수 있으며 하나의 state 를 여러 엘리먼트가 subscribe 할 수 있습니다.

위의 예제 코드는 view를 열고 버튼이 3초 후 생성되며 view의 정보들을 출력하는 예제 코드입니다.

suspend fun openExampleView(vararg players: Player) {
    val view = view("example")
    view.open(*players)
}

view 팩토리 메소드를 통하여 view를 주입받을 수 있습니다.

vjh0107 commented 1 year ago

force push 실수 ㅎ

cccgh5 commented 1 year ago

HQViewHandler 에서 inventoryClose 부분이 있는데 만약, 한 HQView 안에서 다른 HQView 를 연다면 마인크래프트안에선 close 보다 open 이 먼저 발생하기 때문에 해당 이벤트에서 새로 열린 HQView 가 잡힐 수 있습니다.

해서 새로 열린 HQView 가 invokeOnClose 를 실행할 것 같고, 기존의 HQView 는 계속 라이프 사이클이 죽지 않은 상태로 살아있을 것 같아 이 부분을 해결해야 할 것 같습니다.