콘텐츠로 이동

불러오기와 수명

에디터는 플러그인 폴더를 훑어 plugin.json 을 찾습니다. 검사를 통과하고 켜져 있는 플러그인만 불러옵니다. 플러그인 코드에서 예외가 나면 그 플러그인만 끄고 에디터는 계속 돕니다. 이것을 격리라고 합니다.

  1. 폴더 훑기. 플러그인 폴더마다 바로 아래 하위 폴더를 이름 순으로 봅니다. plugin.json 이 있는 폴더만 플러그인입니다.
  2. plugin.json 검사. 칸이 틀리면 「오류(파일)」입니다.
  3. SDK 판 비교. sdk 범위에 에디터 SDK 판이 들지 않으면 「버전 안 맞음」입니다.
  4. 켜짐 확인. 사용자가 끈 플러그인은 「꺼짐」입니다.
  5. 첫 실행 경고. 플러그인을 처음 찾으면 경고 창이 한 번 나옵니다. 「믿을 수 있음 — 불러오기」를 누르면 기억하고 불러옵니다. 「이번엔 안 불러옴」을 누르면 이번에는 아무것도 불러오지 않습니다(「대기」로 남음).
  6. 로드 컨텍스트 만들기. 플러그인마다 따로 만듭니다. dll 은 바이트로 읽어 올리므로 파일을 잠그지 않습니다. 같은 폴더에 .pdb 가 있으면 함께 올립니다.
  7. 진입 클래스 만들기. entry(없으면 첫 INpEditorPlugin 구현)를 인자 없는 생성자로 만듭니다. 클래스를 못 찾거나 만들지 못하면 「오류(격리됨)」입니다.
  8. Register(host) 호출. 예외가 나면 아무것도 등록되지 않고 「오류(격리됨)」입니다.
  9. 에디터에 붙이기. 메뉴 · 모드 · 패널 · 명령 등을 에디터에 붙입니다. 붙이는 중 예외가 나면 「오류(격리됨)」입니다. 이름이 이미 있는 명령 · 액터 타입은 그 항목만 붙지 않습니다.
  10. 켜짐. 출력 로그에 [플러그인 <id>] <판> 켜짐 — <등록 요약> 이 남습니다.

에디터를 켤 때 110 을 한 번 합니다. 플러그인 관리자의 「다시 훑기」나 콘솔 plugins reload 는 110 을 다시 합니다. 이미 켜진 플러그인은 다시 읽지 않습니다.

플러그인 관리자의 「상태」 칸과 콘솔 plugins 결과에 나옵니다.

상태뜻할 일
대기찾았지만 아직 불러오지 않음(경고 확인 전 등)경고를 확인하거나 「다시 훑기」
켜짐정상으로 돌고 있음—
꺼짐사용자가 「켜기」 체크를 끔체크를 다시 켬
오류(파일)plugin.json 이 틀림 · dll 없음 · 같은 id아래 상세 줄의 이유를 고침
버전 안 맞음sdk 범위가 에디터 SDK 를 포함하지 않음. 또는 에디터 SDK 에 없는 멤버를 써서 불러오다 실패sdk 를 고치거나 에디터를 업데이트
오류(격리됨)진입 클래스를 못 만듦 · Register 나 콜백에서 예외 → 그 플러그인만 내림출력 로그의 예외를 고치고 「다시 읽기(선택)」

도구 › 플러그인 관리자… 로 엽니다.

칸 · 단추하는 일
켜기체크하면 켜고(불러옴), 끄면 내립니다. 상태는 plugins_state.json 에 남습니다.
이름 · 버전 · 상태plugin.json 의 name · version 과 지금 상태입니다.
등록켜져 있으면 등록 요약(예 메뉴 1 · 빠른 추가 1), 아니면 오류 이유입니다.
다시 훑기폴더를 다시 봅니다. 새 플러그인을 찾고 없어진 폴더는 내립니다.
다시 읽기(선택)고른 플러그인을 내리고 plugin.json · dll 을 다시 읽어 켭니다. 빌드한 뒤에 씁니다.
폴더 열기사용자 플러그인 폴더를 엽니다.

다음 때 플러그인을 내립니다: 「켜기」 체크 끄기 · 「다시 읽기(선택)」 · 폴더 삭제 후 「다시 훑기」 · 오류 격리. 에디터를 끌 때는 내리기 과정 없이 프로세스가 끝납니다. 그래서 Dispose 에 저장처럼 꼭 해야 하는 일을 두지 않습니다.

  1. 에디터가 등록한 것을 모두 뗍니다(메뉴 · 모드 · 패널 · 명령 · 단축키 · 액터 타입 · 칸 지킴이 · 대칭). 그 플러그인 모드를 쓰던 중이면 블록 브러시 모드로 돌아갑니다.
  2. 진입 클래스가 IDisposable 이면 Dispose() 를 부릅니다.
  3. 월드 로직 type 을 등록부에서 뺍니다. 그 type 을 쓰던 규칙은 지우지 않고 「모르는 type」 주의로 남깁니다.
  4. 플레이 카메라를 쥐고 있었으면 놓습니다(원래 시점으로).
  5. host.Draw 로 그린 것을 지웁니다.
  6. 로드 컨텍스트를 내립니다(collectible 언로드).

host.Settings 값은 파일에 남습니다. 다시 켜면 그대로 읽습니다.

해야 할 것이유
정적 이벤트 · 타이머 · 스레드에 건 것은 Dispose 에서 풉니다.남아 있으면 로드 컨텍스트가 메모리에서 내려가지 않습니다.
Task.Run 으로 돌린 작업은 Dispose 에서 멈추게 합니다(CancellationToken).끈 뒤에 월드를 건드리면 안 됩니다.
host.WorldChanged · Committed · View.PlayTicked 같은 호스트 이벤트는 풀지 않아도 됩니다.내릴 때 호스트가 모두 버립니다.
Dispose 에서 예외를 던지지 않습니다.출력 로그에 남고 내리기는 계속됩니다.
public sealed class Plugin : INpEditorPlugin, IDisposable
{
private readonly CancellationTokenSource _stop = new();
public void Register(INpEditorHost host)
{
// … 등록 …
}
public void Dispose() => _stop.Cancel(); // 배경 작업 멈춤
}
  • 모든 콜백(메뉴 · 명령 · 이벤트 · NpUi · Simulate · PlayTicked)은 에디터 메인 스레드에서 불립니다.
  • 오래 걸리는 계산은 Task.Run 으로 뺍니다.
  • World.Edit 와 Draw 는 메인 스레드(콜백 안)에서만 부릅니다.
무엇위치
사용자 플러그인 폴더%APPDATA%\NPEditor\plugins\<폴더>\ (도구 › 플러그인 폴더 열기)
설치 폴더 플러그인<설치 폴더>\plugins\<폴더>\
더 볼 폴더환경 변수 NP_EDITOR_PLUGINS (여러 개는 ; 로 구분)
켜기 · 끄기 · 경고 확인%APPDATA%\NPEditor\plugins_state.json
플러그인 설정(host.Settings)%APPDATA%\NPEditor\plugin_settings\<id>.json
사용자가 바꾼 단축키%APPDATA%\NPEditor\shortcuts.json
큰 자기 데이터host.PluginDir(=plugin.json 이 있는 폴더) 아래 자기 파일

폴더는 위 표 순서로 훑습니다. 같은 id 는 먼저 찾은 것을 씁니다.

등록한 것에디터 안 이름겹치면
메뉴 · 빠른 추가 · 가져오기/내보내기 · 패널 · 모드 · 단축키ext.<플러그인 id>.<내 id>다른 플러그인 · 내장과 겹치지 않음
단축키 Mode내 모드 id 를 그대로 쓰면 에디터가 ext.<플러그인 id>. 를 붙임—
콘솔 명령 이름그대로(공백 없이)이미 있으면 그 명령만 붙지 않음(먼저 있던 것이 이김)
월드 로직 type그대로(같은 역할 안에서)이미 있으면(내장 포함) 등록 예외 → 그 플러그인 격리
액터 타입 Kind그대로이미 있으면 그 타입만 붙지 않음(먼저 있던 것이 이김)

명령 이름 · 월드 로직 type · 액터 kind 앞에는 내 이름을 붙입니다(예 road_build, myquest_start, myroad.zone).

  • 무한 루프 · 스택 넘침 · 프로세스 종료(Environment.Exit)
  • 파일 · 네트워크 접근

플러그인은 에디터와 같은 권한으로 도는 코드입니다. 격리는 실수를 막는 장치이고 보안 경계가 아닙니다.