Skip to content

feat: Server + Client Light Engine! #304

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 33 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
245300f
init
zardoy Feb 18, 2025
d5c61d8
a working light
zardoy Feb 19, 2025
48ead54
should work.
zardoy Mar 12, 2025
037e297
Merge remote-tracking branch 'origin/next' into light-engine
zardoy Mar 12, 2025
ace45a9
not crash pls
zardoy Mar 13, 2025
ec6b249
Merge remote-tracking branch 'origin/next' into light-engine
zardoy Mar 21, 2025
9f505f8
rm workaround
zardoy Mar 21, 2025
f18b3a1
Merge branch 'next' into light-engine
zardoy Apr 7, 2025
e10f610
humble and terrible progress
zardoy Apr 10, 2025
0fa66e2
Merge remote-tracking branch 'origin/next' into light-engine
zardoy Apr 10, 2025
b1ba2cd
Merge remote-tracking branch 'origin/next' into light-engine
zardoy Apr 12, 2025
3cd1ac3
Merge branch 'next' into light-engine
zardoy Apr 24, 2025
1918c68
finish lighting
zardoy Apr 25, 2025
b4c72db
fix crash opt
zardoy Apr 25, 2025
1f5b682
FINISH OPTIONS, FINISH RECOMPUTE, ADD LIGHT TO WATER
zardoy Apr 28, 2025
5a57d29
Merge branch 'next' into light-engine
zardoy Apr 28, 2025
2f6191a
Merge remote-tracking branch 'origin/next' into light-engine
zardoy Apr 30, 2025
f4eab39
finish lighting
zardoy Apr 30, 2025
79f0fdd
fix lava rendering
zardoy Apr 30, 2025
27c55b1
finish!
zardoy Apr 30, 2025
f4f5edd
fix lighting disabling
zardoy Apr 30, 2025
c97c7e0
finish combined computation, finish settings and strategies
zardoy May 1, 2025
c4b9c33
Update src/optionsStorage.ts
zardoy May 1, 2025
7d224fb
Merge remote-tracking branch 'origin/next' into light-engine
zardoy May 1, 2025
d6f394f
hide cursor block in spectator
zardoy May 2, 2025
ddf0810
final step: move engine to another thread
zardoy May 4, 2025
5720cfa
up light
zardoy May 4, 2025
7dba526
Merge remote-tracking branch 'origin/next' into light-engine
zardoy May 4, 2025
e95f84e
fix lock
zardoy May 4, 2025
90de0d0
up chunk?
zardoy May 4, 2025
f185df9
fix remaining issues with worker bundle with smart approach
zardoy May 8, 2025
6be3c5c
Merge remote-tracking branch 'origin/next' into light-engine
zardoy May 8, 2025
56aee16
Merge branch 'next' into light-engine
zardoy May 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ For building the project yourself / contributing, see [Development, Debugging &

### Big Features

- Connect to Java servers running in both offline (cracked) and online mode* (it's possible because of proxy servers, see below)
- Combined Lighting System - Server Parsing + Client Side Engine for block updates
- Official Mineflayer [plugin integration](https://github.com/zardoy/mcraft-fun-mineflayer-plugin)! View / Control your bot remotely.
- Open any zip world file or even folder in read-write mode!
- Connect to Java servers running in both offline (cracked) and online mode* (it's possible because of proxy servers, see below)
- Integrated JS server clone capable of opening Java world saves in any way (folders, zip, web chunks streaming, etc)
- Singleplayer mode with simple world generations!
- Works offline
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@
"https-browserify": "^1.0.0",
"mc-assets": "^0.2.53",
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
"minecraft-lighting": "^0.0.10",
"mineflayer": "github:zardoy/mineflayer#gen-the-master",
"mineflayer-mouse": "^0.1.10",
"mineflayer-pathfinder": "^2.4.4",
Expand Down
45 changes: 25 additions & 20 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

93 changes: 93 additions & 0 deletions renderer/viewer/lib/lightEngine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { createPrismarineLightEngineWorker } from 'minecraft-lighting'
import { world } from 'prismarine-world'
// import PrismarineWorker from 'minecraft-lighting/dist/prismarineWorker.worker.js'
import { WorldDataEmitter } from './worldDataEmitter'
import { initMesherWorker, meshersSendMcData } from './worldrendererCommon'

let lightEngineNew: ReturnType<typeof createPrismarineLightEngineWorker> | null = null

export const getLightEngineSafe = () => {
// return lightEngine
return lightEngineNew
}

export const createLightEngineIfNeededNew = (worldView: WorldDataEmitter, version: string) => {
if (lightEngineNew) return
const worker = initMesherWorker((data) => {
// console.log('light engine worker message', data)
})
meshersSendMcData([worker], version)
worker.postMessage({ type: 'sideControl', value: 'lightEngine' })
lightEngineNew = createPrismarineLightEngineWorker(worker, worldView.world as unknown as world.WorldSync, loadedData)
lightEngineNew.initialize({
minY: worldView.minY,
height: worldView.minY + worldView.worldHeight,
// writeLightToOriginalWorld: true,
// enableSkyLight: false,
})

globalThis.lightEngine = lightEngineNew
}

export const processLightChunk = async (x: number, z: number, doLighting: boolean) => {
const engine = getLightEngineSafe()
if (!engine) return

const chunkX = Math.floor(x / 16)
const chunkZ = Math.floor(z / 16)
// fillColumnWithZeroLight(engine.externalWorld, chunkX, chunkZ)

const updated = await engine.loadChunk(chunkX, chunkZ, doLighting)
return updated
}

export const dumpLightData = (x: number, z: number) => {
const engine = getLightEngineSafe()
// return engine?.worldLightHolder.dumpChunk(Math.floor(x / 16), Math.floor(z / 16))
}

export const getDebugLightValues = (x: number, y: number, z: number) => {
const engine = getLightEngineSafe()
// return {
// blockLight: engine?.worldLightHolder.getBlockLight(x, y, z) ?? -1,
// skyLight: engine?.worldLightHolder.getSkyLight(x, y, z) ?? -1,
// }
}

export const updateBlockLight = async (x: number, y: number, z: number, stateId: number, distance: number) => {

Check warning on line 57 in renderer/viewer/lib/lightEngine.ts

View workflow job for this annotation

GitHub Actions / build-and-deploy

Async arrow function has too many parameters (5). Maximum allowed is 4
if (distance > 16) return []
const chunkX = Math.floor(x / 16) * 16
const chunkZ = Math.floor(z / 16) * 16
const engine = getLightEngineSafe()
if (!engine) return
const start = performance.now()
const result = await engine.setBlock(x, y, z, stateId)
const end = performance.now()
console.log(`[light engine] updateBlockLight (${x}, ${y}, ${z}) took`, Math.round(end - start), 'ms', result.length, 'chunks')
return result
Comment on lines +57 to +67
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for light engine operations

The updateBlockLight function logs performance data but doesn't handle errors.

Add try/catch block to handle potential errors from the light engine:

export const updateBlockLight = async (x: number, y: number, z: number, stateId: number, distance: number) => {
  if (distance > 16) return []
  const chunkX = Math.floor(x / 16) * 16
  const chunkZ = Math.floor(z / 16) * 16
  const engine = getLightEngineSafe()
  if (!engine) return
  const start = performance.now()
+  try {
    const result = await engine.setBlock(x, y, z, stateId)
    const end = performance.now()
    console.log(`[light engine] updateBlockLight (${x}, ${y}, ${z}) took`, Math.round(end - start), 'ms', result.length, 'chunks')
    return result
+  } catch (error) {
+    console.error(`[light engine] Error updating block light at (${x}, ${y}, ${z}):`, error)
+    return []
+  }
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const updateBlockLight = async (x: number, y: number, z: number, stateId: number, distance: number) => {
if (distance > 16) return []
const chunkX = Math.floor(x / 16) * 16
const chunkZ = Math.floor(z / 16) * 16
const engine = getLightEngineSafe()
if (!engine) return
const start = performance.now()
const result = await engine.setBlock(x, y, z, stateId)
const end = performance.now()
console.log(`[light engine] updateBlockLight (${x}, ${y}, ${z}) took`, Math.round(end - start), 'ms', result.length, 'chunks')
return result
export const updateBlockLight = async (
x: number,
y: number,
z: number,
stateId: number,
distance: number
) => {
if (distance > 16) return []
const chunkX = Math.floor(x / 16) * 16
const chunkZ = Math.floor(z / 16) * 16
const engine = getLightEngineSafe()
if (!engine) return
const start = performance.now()
try {
const result = await engine.setBlock(x, y, z, stateId)
const end = performance.now()
console.log(
`[light engine] updateBlockLight (${x}, ${y}, ${z}) took`,
Math.round(end - start),
'ms',
result.length,
'chunks'
)
return result
} catch (error) {
console.error(
`[light engine] Error updating block light at (${x}, ${y}, ${z}):`,
error
)
return []
}
}


// const engine = getLightEngineSafe()
// if (!engine) return
// const affected = engine['affectedChunksTimestamps'] as Map<string, number>
// const noAffected = affected.size === 0
// engine.setBlock(x, y, z, convertPrismarineBlockToWorldBlock(stateId, loadedData))

// if (affected.size > 0) {
// const chunks = [...affected.keys()].map(key => {
// return key.split(',').map(Number) as [number, number]
// })
// affected.clear()
// return chunks
// }
}

export const lightRemoveColumn = (x: number, z: number) => {
const engine = getLightEngineSafe()
if (!engine) return
engine.unloadChunk(Math.floor(x / 16), Math.floor(z / 16))
}

export const destroyLightEngine = () => {
lightEngineNew = null
globalThis.lightEngine = null
}
13 changes: 13 additions & 0 deletions renderer/viewer/lib/mesher/mesher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ const softCleanup = () => {
globalThis.world = world
}

let sideControl = false
const handleMessage = data => {
if (sideControl) return

const globalVar: any = globalThis

if (data.type === 'mcData') {
Expand All @@ -92,6 +95,13 @@ const handleMessage = data => {
}

switch (data.type) {
case 'sideControl': {
if (data.value === 'lightEngine') {
sideControl = true
import('minecraft-lighting/dist/prismarineWorker.worker.js')
}
break
}
case 'mesherData': {
setMesherData(data.blockstatesModels, data.blocksAtlas, data.config.outputFormat === 'webgpu')
allDataReady = true
Expand All @@ -107,6 +117,9 @@ const handleMessage = data => {
}
case 'chunk': {
world.addColumn(data.x, data.z, data.chunk)
if (data.lightData) {
world.lightHolder.loadChunk(data.lightData)
}
if (data.customBlockModels) {
const chunkKey = `${data.x},${data.z}`
world.customBlockModels.set(chunkKey, data.customBlockModels)
Expand Down
3 changes: 3 additions & 0 deletions renderer/viewer/lib/mesher/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
return [r / 255, g / 255, b / 255]
}

function getLiquidRenderHeight (world: World, block: WorldBlock | null, type: number, pos: Vec3, isWater: boolean, isRealWater: boolean) {

Check warning on line 106 in renderer/viewer/lib/mesher/models.ts

View workflow job for this annotation

GitHub Actions / build-and-deploy

Function 'getLiquidRenderHeight' has too many parameters (6). Maximum allowed is 4
if ((isWater && !isRealWater) || (block && isBlockWaterlogged(block))) return 8 / 9
if (!block || block.type !== type) return 1 / 9
if (block.metadata === 0) { // source block
Expand Down Expand Up @@ -132,7 +132,7 @@
return v.plus(dir)
}

function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, type: number, biome: string, water: boolean, attr: Record<string, any>, isRealWater: boolean) {

Check warning on line 135 in renderer/viewer/lib/mesher/models.ts

View workflow job for this annotation

GitHub Actions / build-and-deploy

Function 'renderLiquid' has too many parameters (8). Maximum allowed is 4
const heights: number[] = []
for (let z = -1; z <= 1; z++) {
for (let x = -1; x <= 1; x++) {
Expand Down Expand Up @@ -488,6 +488,7 @@

let unknownBlockModel: BlockModelPartsResolved
export function getSectionGeometry (sx, sy, sz, world: World) {
world.hadSkyLight = false
let delayedRender = [] as Array<() => void>

const attr: MesherGeometryOutput = {
Expand Down Expand Up @@ -691,6 +692,8 @@
delete attr.uvs
}

attr.hasSkylight = world.hadSkyLight

return attr
}

Expand Down
5 changes: 5 additions & 0 deletions renderer/viewer/lib/mesher/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import { BlockType } from '../../../playground/shared'
// only here for easier testing
export const defaultMesherConfig = {
version: '',

enableLighting: true,
skyLight: 15,
smoothLighting: true,
usingCustomLightHolder: false,
flyingSquidWorkarounds: false,

outputFormat: 'threeJs' as 'threeJs' | 'webgpu',
textureSize: 1024, // for testing
debugModelVariant: undefined as undefined | number[],
Expand Down Expand Up @@ -44,6 +48,7 @@ export type MesherGeometryOutput = {
hadErrors: boolean
blocksCount: number
customBlockModels?: CustomBlockModels
hasSkylight?: boolean
}

export type HighestBlockInfo = { y: number, stateId: number | undefined, biomeId: number | undefined }
Expand Down
Loading
Loading