Skip to content

Commit 0626a4f

Browse files
committed
editor updates
1 parent fc3ea11 commit 0626a4f

20 files changed

+1263
-107
lines changed

bun.lock

Lines changed: 208 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,13 @@
4040
"@codemirror/lang-json": "^6.0.2",
4141
"@codemirror/language": "^6.11.3",
4242
"@codemirror/lint": "^6.8.5",
43+
"@codemirror/lsp-client": "^6.1.1",
4344
"@codemirror/search": "^6.5.11",
4445
"@codemirror/view": "^6.38.1",
4546
"@minecraftmetascript/mms-wasm": "../mms/result/js",
47+
"@spyglassmc/mcdoc": "^0.3.38",
48+
"@steeze-ui/svelte-icon": "^1.6.2",
49+
"@steeze-ui/tabler-icons": "^3.3.1",
4650
"@sveltejs/adapter-static": "^3.0.9",
4751
"@types/golang-wasm-exec": "^1.15.2",
4852
"codemirror": "^6.0.2",

src/lib/MMSProject.svelte.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Go, type FileTreeLike, type MmsSymbol } from '@minecraftmetascript/mms-wasm';
22
import MMSWasm from '@minecraftmetascript/mms-wasm/dist/main.wasm?init';
3+
import * as deepslate from 'deepslate';
34

45
export class MMSFile {
56
private _content: string;
@@ -44,7 +45,7 @@ export class MMSProject {
4445
signal?.addEventListener('abort', () => {
4546
console.log('Abort Signal Received. Quitting MMS');
4647
this.goInstance.exit(1);
47-
});
48+
});
4849
}
4950

5051
constructor() {
@@ -70,6 +71,19 @@ export class MMSProject {
7071
get symbols() {
7172
return this._symbols;
7273
}
74+
private set symbols(next: Record<string, MmsSymbol> | null) {
75+
for (const [k, v] of Object.entries(next ?? {})) {
76+
switch (v.type) {
77+
case 'Noise': {
78+
deepslate.WorldgenRegistries.NOISE.register(
79+
new deepslate.Identifier(...(v.ref.split(':') as [string, string])),
80+
deepslate.NoiseParameters.fromJson(v.value)
81+
);
82+
}
83+
}
84+
}
85+
this._symbols = next;
86+
}
7387

7488
private _source: Record<string, string>;
7589
get source() {
@@ -79,7 +93,7 @@ export class MMSProject {
7993
readonly onProjectUpdate = (proj: string) => {
8094
const data = JSON.parse(proj);
8195
this._fs = data.files;
82-
this._symbols = data.symbols;
96+
this.symbols = data.symbols;
8397
this._source = data.source;
8498
};
8599

Lines changed: 115 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,129 @@
11
<script lang="ts" module>
2-
export const SupportedPreviewKinds = ['Noise'];
2+
export const SupportedPreviewKinds = ['Noise', 'DensityFunction'];
33
</script>
44

55
<script lang="ts">
66
import type { MmsSymbol } from '@minecraftmetascript/mms-wasm';
77
8-
import * as deepslate from 'deepslate';
9-
import { debounce } from 'es-toolkit';
10-
import type { Action } from 'svelte/action';
8+
import { MMSProject } from '../MMSProject.svelte';
9+
import type { MouseEventHandler } from 'svelte/elements';
10+
import { Icon } from '@steeze-ui/svelte-icon';
11+
import {
12+
ArrowDown,
13+
ArrowLeft,
14+
ArrowRight,
15+
ArrowUp,
16+
Lock,
17+
Minus,
18+
Plus
19+
} from '@steeze-ui/tabler-icons';
20+
import { deepslateCanvas } from './deepslate.svelte';
21+
import type { ValueLookupFn } from './types';
1122
12-
let { symbol }: { symbol: MmsSymbol } = $props();
23+
let { symbol }: { symbol: MmsSymbol; project: MMSProject } = $props();
24+
let seed = [
25+
BigInt(Math.floor(performance.now())),
26+
BigInt(Math.floor(performance.timeOrigin))
27+
] as [bigint, bigint];
1328
14-
const deepslatePreview: Action<HTMLCanvasElement, MmsSymbol> = (
15-
canvas: HTMLCanvasElement,
16-
s: MmsSymbol
17-
) => {
18-
let symbol = s;
29+
let scale = $state(20);
30+
let originX = $state(0);
31+
let originY = $state(0);
32+
let origin = $derived({ x: originX, y: originY });
33+
let worldHeight = $state(256);
1934
20-
const update = debounce(() => {
21-
const random = new deepslate.XoroshiroRandom([
22-
BigInt(Math.floor(performance.now())),
23-
BigInt(Math.floor(performance.timeOrigin))
24-
]);
35+
let mousePos = $state<{ x: number; y: number } | null>(null);
36+
let valueLookupFn = $state<ValueLookupFn | null>(null);
37+
let lookupLabel = $derived.by(() => (mousePos && valueLookupFn ? valueLookupFn(mousePos) : null));
2538
26-
if (!canvas) {
27-
console.warn('No Canvas');
28-
return;
29-
}
30-
if (!symbol || !symbol.value) {
31-
console.warn('No Symbol or Value');
32-
return;
33-
}
34-
const ctx = canvas.getContext('2d');
35-
if (!ctx) {
36-
console.warn('No Canvas 2d Context');
37-
return;
38-
}
39-
// TypeScript
40-
const dpr = window.devicePixelRatio || 1;
41-
const rect = canvas.getBoundingClientRect();
42-
43-
// Set intrinsic size to match CSS size * DPR
44-
canvas.width = Math.floor(rect.width * dpr);
45-
canvas.height = Math.floor(rect.height * dpr);
46-
47-
// Scale the context so 1 unit = 1 CSS pixel
48-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
49-
ctx.scale(3, 3);
50-
51-
switch (symbol.type) {
52-
case 'Noise': {
53-
requestAnimationFrame(() => {
54-
try {
55-
console.log(symbol.value);
56-
const normalNoise = new deepslate.NormalNoise(random, symbol.value as any);
57-
for (let pixelX = 0; pixelX < rect.width; pixelX += 1) {
58-
for (let pixelY = 0; pixelY < rect.height; pixelY += 1) {
59-
const val = normalNoise.sample(pixelX, pixelY, 0);
60-
const color = `hsl(0, ${val * 50 + 50}%, ${val * 20 + 40}%)`;
61-
ctx.fillStyle = color;
62-
ctx.fillRect(pixelX, pixelY, 1, 1);
63-
}
64-
}
65-
} catch (e) {
66-
console.warn(e);
67-
}
68-
});
69-
}
70-
}
71-
}, 250);
72-
73-
update();
74-
75-
window.addEventListener('resize', update);
76-
77-
return {
78-
update(next: MmsSymbol) {
79-
symbol = next;
80-
update();
81-
},
82-
destroy() {
83-
window.removeEventListener('resize', update);
84-
}
39+
let mouseLocked = $state(false);
40+
const updateMousePos: MouseEventHandler<HTMLCanvasElement> = (e) => {
41+
if (mouseLocked) return;
42+
const box = e.currentTarget.getBoundingClientRect();
43+
mousePos = {
44+
x: Math.floor((e.pageX - box.left) / scale),
45+
y: Math.floor((box.height - (e.pageY - box.top)) / scale)
8546
};
8647
};
48+
const clearMousePos = () => {
49+
if (mouseLocked) return;
50+
mousePos = null;
51+
};
52+
53+
const toggleMouseLock = () => {
54+
mouseLocked = !mouseLocked;
55+
};
8756
</script>
8857

89-
<canvas class="h-full w-full" use:deepslatePreview={symbol}></canvas>
58+
<div class="relative h-full w-full">
59+
{#if mousePos || lookupLabel}
60+
<div
61+
class="absolute top-2 right-2 flex items-center gap-2 bg-slate-700 px-2 py-1 font-mono text-sm text-slate-50"
62+
>
63+
{#if mouseLocked}
64+
<Icon src={Lock} class="w-4" />
65+
{/if}
66+
{#if mousePos}
67+
<span>X: {mousePos?.x + origin.x}</span>
68+
<span>Y: {mousePos?.y + origin.y}</span>
69+
{/if}
70+
{#if lookupLabel}
71+
<span>{lookupLabel?.label}: {lookupLabel?.value}</span>
72+
{/if}
73+
</div>
74+
{/if}
75+
<div class="absolute top-2 left-2 flex flex-col items-center gap-2">
76+
<button class="bg-slate-600/50 text-slate-50 hover:bg-slate-600" onclick={() => scale++}>
77+
<Icon src={Plus} class="w-6" />
78+
</button>
79+
<button class="bg-slate-600/50 text-slate-50 hover:bg-slate-600" onclick={() => scale--}>
80+
<Icon src={Minus} class="w-6" />
81+
</button>
82+
</div>
83+
84+
<div class="absolute bottom-2 left-2 grid grid-cols-3 grid-rows-3">
85+
<button
86+
class="col-start-2 row-start-1 bg-slate-600/50 text-slate-50 hover:bg-slate-600"
87+
onclick={() => (originY += scale * 5)}
88+
>
89+
<Icon src={ArrowUp} class="w-6" />
90+
</button>
91+
<button
92+
class="col-start-1 row-start-2 bg-slate-600/50 text-slate-50 hover:bg-slate-600"
93+
onclick={() => (originX -= scale * 5)}
94+
>
95+
<Icon src={ArrowLeft} class="w-6" />
96+
</button>
97+
<button
98+
class="col-start-3 row-start-2 bg-slate-600/50 text-slate-50 hover:bg-slate-600"
99+
onclick={() => (originX += scale * 5)}
100+
>
101+
<Icon src={ArrowRight} class="w-6" />
102+
</button>
103+
<button
104+
class="col-start-2 row-start-3 bg-slate-600/50 text-slate-50 hover:bg-slate-600"
105+
onclick={() => (originY -= scale * 5)}
106+
>
107+
<Icon src={ArrowDown} class="w-6" />
108+
</button>
109+
</div>
110+
<canvas
111+
class="h-full w-full cursor-pointer"
112+
{@attach deepslateCanvas({
113+
symbol,
114+
seed,
115+
scale,
116+
origin,
117+
setLookup(fn) {
118+
valueLookupFn = fn;
119+
}
120+
})}
121+
onmousemove={updateMousePos}
122+
onmouseout={clearMousePos}
123+
onblur={clearMousePos}
124+
onclick={toggleMouseLock}
125+
onmousedown={() => {
126+
originX++;
127+
}}
128+
></canvas>
129+
</div>
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import type { MmsSymbol } from '@minecraftmetascript/mms-wasm';
2+
3+
import * as deepslate from 'deepslate';
4+
import { debounce } from 'es-toolkit';
5+
import type { Attachment } from 'svelte/attachments';
6+
import type { ValueLookupFn } from './types';
7+
import { densityFunctionPreview, noisePreview } from './previews.svelte';
8+
9+
export const deepslateCanvas: (params: {
10+
symbol: MmsSymbol;
11+
seed: [bigint, bigint];
12+
scale: number;
13+
setLookup: (fn: ValueLookupFn | null) => void;
14+
origin: { x: number; y: number };
15+
}) => Attachment<HTMLCanvasElement> = (params) => {
16+
let dpr = window.devicePixelRatio || 1;
17+
18+
return (canvas) => {
19+
let { symbol, seed, scale, setLookup, origin } = params;
20+
let rect = canvas.getBoundingClientRect();
21+
if (scale <= 0.5) scale = 0.5;
22+
// canvas.width = Math.round(rect.width * dpr);
23+
// canvas.height = Math.round(rect.height * dpr);
24+
const update = debounce(() => {
25+
updating = true;
26+
try {
27+
const random = new deepslate.XoroshiroRandom(seed);
28+
switch (symbol.type) {
29+
case 'DensityFunction':
30+
setLookup(
31+
densityFunctionPreview(canvas, symbol.value, random, {
32+
height: rect.height / scale,
33+
width: rect.width / scale,
34+
scale: scale,
35+
dpr: dpr,
36+
origin: origin
37+
})
38+
);
39+
break;
40+
case 'Noise':
41+
setLookup(
42+
noisePreview(canvas, symbol.value, random, {
43+
height: rect.height / scale,
44+
width: rect.width / scale,
45+
scale: scale,
46+
dpr: dpr,
47+
origin: origin
48+
})
49+
);
50+
break;
51+
}
52+
} finally {
53+
updating = false;
54+
}
55+
}, 200);
56+
update();
57+
58+
let updating = false;
59+
const resizeObeserver = new ResizeObserver((els) => {
60+
if (updating) return;
61+
for (const el of els) {
62+
if (el.contentRect.width !== rect.width || el.contentRect.height !== rect.height) {
63+
resizeObeserver.unobserve(canvas);
64+
// console.log('Stop');
65+
// let rect = canvas.getBoundingClientRect();
66+
// rect = el.contentRect;
67+
// console.log('Start');
68+
69+
update();
70+
setTimeout(() => resizeObeserver.observe(canvas), 500);
71+
}
72+
}
73+
});
74+
resizeObeserver.observe(canvas);
75+
window.addEventListener('resize', update);
76+
return () => {
77+
window.removeEventListener('resize', update);
78+
resizeObeserver.disconnect();
79+
};
80+
};
81+
};
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
export const drawValues = (
2+
fn: (x: number, y: number) => number,
3+
canvas: HTMLCanvasElement,
4+
{
5+
width,
6+
height,
7+
scale,
8+
dpr,
9+
origin
10+
}: { width: number; height: number; scale: number; dpr: number; origin: { x: number; y: number } }
11+
) => {
12+
const vals: Record<number, Record<number, number>> = {};
13+
14+
for (let pixelX = 0; pixelX < width; pixelX += 1) {
15+
vals[pixelX] = {};
16+
for (let pixelY = 0; pixelY < height; pixelY += 1) {
17+
const coordX = pixelX + origin.x;
18+
const coordY = pixelY + origin.y;
19+
vals[pixelX][pixelY] = fn(coordX, Math.floor(height - coordY));
20+
}
21+
}
22+
23+
requestAnimationFrame(() => {
24+
const ctx = canvas.getContext('2d');
25+
if (!ctx) return;
26+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
27+
ctx.scale(scale, scale);
28+
29+
// ctx.fillText('.', 0, i, scale);
30+
// for (let i = 0; i < 10; i++) {
31+
// ctx.fillStyle = 'black';
32+
33+
// console.log(i);
34+
// }
35+
36+
for (let pixelX = 0; pixelX < width; pixelX += 1) {
37+
for (let pixelY = 0; pixelY < height; pixelY += 1) {
38+
writeColor(vals[pixelX][pixelY], ctx, pixelX, Math.floor(height - pixelY), scale * 2);
39+
}
40+
}
41+
});
42+
};
43+
export const writeColor = (
44+
val: number,
45+
ctx: CanvasRenderingContext2D,
46+
pixelX: number,
47+
pixelY: number,
48+
scale: number
49+
) => {
50+
const color = `hsl(0, ${(val + 1) * 50}%, 50%)`;
51+
ctx.fillStyle = color;
52+
ctx.fillRect(pixelX * scale, pixelY * scale, scale, scale);
53+
};

0 commit comments

Comments
 (0)