mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-17 03:15:35 -04:00
rendering: Improve HUD structure, internal changes
* Before joining a server rendering will be started now. * Rendering now uses (partly) the modding api
This commit is contained in:
parent
14a7ad6e61
commit
edcc288898
@ -43,6 +43,9 @@ public abstract class ChatComponent {
|
|||||||
if (raw == null) {
|
if (raw == null) {
|
||||||
return new BaseComponent();
|
return new BaseComponent();
|
||||||
}
|
}
|
||||||
|
if (raw instanceof ChatComponent component) {
|
||||||
|
return component;
|
||||||
|
}
|
||||||
if (raw instanceof JsonPrimitive primitive) {
|
if (raw instanceof JsonPrimitive primitive) {
|
||||||
raw = primitive.getAsString();
|
raw = primitive.getAsString();
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,11 @@ import de.bixilon.minosoft.data.entities.EntityRotation
|
|||||||
import de.bixilon.minosoft.data.entities.Location
|
import de.bixilon.minosoft.data.entities.Location
|
||||||
import de.bixilon.minosoft.gui.rendering.chunk.ChunkRenderer
|
import de.bixilon.minosoft.gui.rendering.chunk.ChunkRenderer
|
||||||
import de.bixilon.minosoft.gui.rendering.hud.HUDRenderer
|
import de.bixilon.minosoft.gui.rendering.hud.HUDRenderer
|
||||||
|
import de.bixilon.minosoft.gui.rendering.hud.elements.RenderStats
|
||||||
import de.bixilon.minosoft.protocol.network.Connection
|
import de.bixilon.minosoft.protocol.network.Connection
|
||||||
import de.bixilon.minosoft.protocol.packets.serverbound.play.PacketPlayerPositionAndRotationSending
|
import de.bixilon.minosoft.protocol.packets.serverbound.play.PacketPlayerPositionAndRotationSending
|
||||||
import de.bixilon.minosoft.util.CountUpAndDownLatch
|
import de.bixilon.minosoft.util.CountUpAndDownLatch
|
||||||
|
import de.bixilon.minosoft.util.logging.Log
|
||||||
import org.lwjgl.*
|
import org.lwjgl.*
|
||||||
import org.lwjgl.glfw.Callbacks
|
import org.lwjgl.glfw.Callbacks
|
||||||
import org.lwjgl.glfw.GLFW.*
|
import org.lwjgl.glfw.GLFW.*
|
||||||
@ -17,26 +19,27 @@ import org.lwjgl.opengl.GL11.*
|
|||||||
import org.lwjgl.system.MemoryStack
|
import org.lwjgl.system.MemoryStack
|
||||||
import org.lwjgl.system.MemoryUtil
|
import org.lwjgl.system.MemoryUtil
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
class RenderWindow(private val connection: Connection, val rendering: Rendering) {
|
class RenderWindow(private val connection: Connection, val rendering: Rendering) {
|
||||||
private var screenWidth = 800
|
val renderStats = RenderStats()
|
||||||
private var screenHeight = 600
|
var screenWidth = 800
|
||||||
|
var screenHeight = 600
|
||||||
private var polygonEnabled = false
|
private var polygonEnabled = false
|
||||||
private var windowId: Long = 0
|
private var windowId: Long = 0
|
||||||
private var deltaTime = 0.0 // time between current frame and last frame
|
private var deltaTime = 0.0 // time between current frame and last frame
|
||||||
|
|
||||||
private var lastFrame = 0.0
|
private var lastFrame = 0.0
|
||||||
lateinit var camera: Camera
|
lateinit var camera: Camera
|
||||||
|
var latch = CountUpAndDownLatch(1)
|
||||||
|
|
||||||
// all renderers
|
// all renderers
|
||||||
val chunkRenderer: ChunkRenderer = ChunkRenderer(connection.player.world, this)
|
val chunkRenderer: ChunkRenderer = ChunkRenderer(connection, connection.player.world, this)
|
||||||
val hudRenderer: HUDRenderer = HUDRenderer(connection, this)
|
val hudRenderer: HUDRenderer = HUDRenderer(connection, this)
|
||||||
|
|
||||||
val renderQueue = ConcurrentLinkedQueue<Runnable>()
|
val renderQueue = ConcurrentLinkedQueue<Runnable>()
|
||||||
|
|
||||||
|
|
||||||
fun init(latch: CountUpAndDownLatch? = null) {
|
fun init(latch: CountUpAndDownLatch) {
|
||||||
// Setup an error callback. The default implementation
|
// Setup an error callback. The default implementation
|
||||||
// will print the error message in System.err.
|
// will print the error message in System.err.
|
||||||
GLFWErrorCallback.createPrint(System.err).set()
|
GLFWErrorCallback.createPrint(System.err).set()
|
||||||
@ -107,9 +110,9 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering)
|
|||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
||||||
|
|
||||||
|
|
||||||
chunkRenderer.init(connection)
|
chunkRenderer.init()
|
||||||
|
|
||||||
hudRenderer.init(connection)
|
hudRenderer.init()
|
||||||
|
|
||||||
|
|
||||||
glfwSetWindowSizeCallback(windowId, object : GLFWWindowSizeCallback() {
|
glfwSetWindowSizeCallback(windowId, object : GLFWWindowSizeCallback() {
|
||||||
@ -128,23 +131,22 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering)
|
|||||||
camera.calculateProjectionMatrix(screenWidth, screenHeight, chunkRenderer.chunkShader)
|
camera.calculateProjectionMatrix(screenWidth, screenHeight, chunkRenderer.chunkShader)
|
||||||
camera.calculateViewMatrix(chunkRenderer.chunkShader)
|
camera.calculateViewMatrix(chunkRenderer.chunkShader)
|
||||||
|
|
||||||
glfwShowWindow(windowId)
|
|
||||||
|
|
||||||
latch?.countDown()
|
Log.debug("Rendering is prepared and ready to go!")
|
||||||
|
latch.countDown()
|
||||||
|
latch.waitUntilZero()
|
||||||
|
this.latch.waitUntilZero()
|
||||||
|
glfwShowWindow(windowId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startRenderLoop() {
|
fun startRenderLoop() {
|
||||||
var framesLastSecond = 0
|
|
||||||
var lastCalcTime = glfwGetTime()
|
|
||||||
var frameTimeLastCalc = 0.0
|
|
||||||
|
|
||||||
var lastPositionChangeTime = 0.0
|
var lastPositionChangeTime = 0.0
|
||||||
|
|
||||||
while (!glfwWindowShouldClose(windowId)) {
|
while (!glfwWindowShouldClose(windowId)) {
|
||||||
|
renderStats.startFrame()
|
||||||
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) // clear the framebuffer
|
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) // clear the framebuffer
|
||||||
|
|
||||||
val currentFrame = glfwGetTime()
|
val currentFrame = glfwGetTime()
|
||||||
|
|
||||||
deltaTime = currentFrame - lastFrame
|
deltaTime = currentFrame - lastFrame
|
||||||
lastFrame = currentFrame
|
lastFrame = currentFrame
|
||||||
|
|
||||||
@ -153,26 +155,15 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering)
|
|||||||
|
|
||||||
|
|
||||||
chunkRenderer.draw()
|
chunkRenderer.draw()
|
||||||
|
|
||||||
hudRenderer.draw()
|
hudRenderer.draw()
|
||||||
|
|
||||||
|
renderStats.endDraw()
|
||||||
|
|
||||||
|
|
||||||
glfwSwapBuffers(windowId)
|
glfwSwapBuffers(windowId)
|
||||||
|
|
||||||
glfwPollEvents()
|
glfwPollEvents()
|
||||||
|
|
||||||
camera.handleInput(deltaTime)
|
camera.handleInput(deltaTime)
|
||||||
|
|
||||||
frameTimeLastCalc += glfwGetTime() - currentFrame
|
|
||||||
|
|
||||||
if (glfwGetTime() - lastCalcTime >= 0.25) {
|
|
||||||
glfwSetWindowTitle(windowId, "Minosoft | FPS: ${framesLastSecond * 4} (${(0.25 * framesLastSecond / (frameTimeLastCalc)).roundToInt()})")
|
|
||||||
hudRenderer.fps = framesLastSecond * 4
|
|
||||||
lastCalcTime = glfwGetTime()
|
|
||||||
framesLastSecond = 0
|
|
||||||
frameTimeLastCalc = 0.0
|
|
||||||
}
|
|
||||||
framesLastSecond++
|
|
||||||
|
|
||||||
if (glfwGetTime() - lastPositionChangeTime > 0.05) {
|
if (glfwGetTime() - lastPositionChangeTime > 0.05) {
|
||||||
// ToDo: Replace this with proper movement and only send it, when our position changed
|
// ToDo: Replace this with proper movement and only send it, when our position changed
|
||||||
@ -181,10 +172,13 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// handle opengl context tasks
|
||||||
for (renderQueueElement in renderQueue) {
|
for (renderQueueElement in renderQueue) {
|
||||||
renderQueueElement.run()
|
renderQueueElement.run()
|
||||||
renderQueue.remove(renderQueueElement)
|
renderQueue.remove(renderQueueElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderStats.endFrame()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package de.bixilon.minosoft.gui.rendering
|
package de.bixilon.minosoft.gui.rendering
|
||||||
|
|
||||||
import de.bixilon.minosoft.protocol.network.Connection
|
|
||||||
|
|
||||||
interface Renderer {
|
interface Renderer {
|
||||||
|
fun init()
|
||||||
fun init(connection: Connection)
|
|
||||||
|
|
||||||
fun draw()
|
fun draw()
|
||||||
|
fun screenChangeResizeCallback(width: Int, height: Int) {}
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,10 @@ import java.util.concurrent.Executors
|
|||||||
|
|
||||||
class Rendering(private val connection: Connection) {
|
class Rendering(private val connection: Connection) {
|
||||||
val renderWindow: RenderWindow = RenderWindow(connection, this)
|
val renderWindow: RenderWindow = RenderWindow(connection, this)
|
||||||
val latch = CountUpAndDownLatch(1)
|
|
||||||
val executor: ExecutorService = Executors.newFixedThreadPool(4, Util.getThreadFactory(String.format("Rendering#%d", connection.connectionId)))
|
val executor: ExecutorService = Executors.newFixedThreadPool(4, Util.getThreadFactory(String.format("Rendering#%d", connection.connectionId)))
|
||||||
|
|
||||||
fun start() {
|
fun start(latch: CountUpAndDownLatch) {
|
||||||
|
latch.countUp()
|
||||||
Thread({
|
Thread({
|
||||||
Log.info("Hello LWJGL " + Version.getVersion() + "!")
|
Log.info("Hello LWJGL " + Version.getVersion() + "!")
|
||||||
renderWindow.init(latch)
|
renderWindow.init(latch)
|
||||||
@ -27,6 +27,10 @@ class Rendering(private val connection: Connection) {
|
|||||||
|
|
||||||
|
|
||||||
fun teleport(position: Location) {
|
fun teleport(position: Location) {
|
||||||
|
// tell the window we are ready (received position)
|
||||||
|
if (renderWindow.latch.count > 0) {
|
||||||
|
renderWindow.latch.countDown()
|
||||||
|
}
|
||||||
renderWindow.renderQueue.add {
|
renderWindow.renderQueue.add {
|
||||||
renderWindow.camera.cameraPosition = Vec3(position.x, position.y, position.z)
|
renderWindow.camera.cameraPosition = Vec3(position.x, position.y, position.z)
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ import org.lwjgl.opengl.GL11.glEnable
|
|||||||
import org.lwjgl.opengl.GL13.GL_TEXTURE0
|
import org.lwjgl.opengl.GL13.GL_TEXTURE0
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
class ChunkRenderer(private val world: World, val renderWindow: RenderWindow) : Renderer {
|
class ChunkRenderer(private val connection: Connection, private val world: World, val renderWindow: RenderWindow) : Renderer {
|
||||||
private lateinit var minecraftTextures: TextureArray
|
private lateinit var minecraftTextures: TextureArray
|
||||||
lateinit var chunkShader: Shader
|
lateinit var chunkShader: Shader
|
||||||
private val chunkSectionsToDraw = ConcurrentHashMap<ChunkLocation, ConcurrentHashMap<Int, WorldMesh>>()
|
private val chunkSectionsToDraw = ConcurrentHashMap<ChunkLocation, ConcurrentHashMap<Int, WorldMesh>>()
|
||||||
@ -81,7 +81,7 @@ class ChunkRenderer(private val world: World, val renderWindow: RenderWindow) :
|
|||||||
return data.toFloatArray()
|
return data.toFloatArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun init(connection: Connection) {
|
override fun init() {
|
||||||
minecraftTextures = TextureArray.createTextureArray(connection.version.assetsManager, resolveBlockTextureIds(connection.version.mapping.blockMap.values), 16, 16) // ToDo :Remove fixed size
|
minecraftTextures = TextureArray.createTextureArray(connection.version.assetsManager, resolveBlockTextureIds(connection.version.mapping.blockMap.values), 16, 16) // ToDo :Remove fixed size
|
||||||
minecraftTextures.load()
|
minecraftTextures.load()
|
||||||
|
|
||||||
@ -124,7 +124,6 @@ class ChunkRenderer(private val world: World, val renderWindow: RenderWindow) :
|
|||||||
|
|
||||||
fun prepareChunkSection(chunkLocation: ChunkLocation, sectionHeight: Int, section: ChunkSection) {
|
fun prepareChunkSection(chunkLocation: ChunkLocation, sectionHeight: Int, section: ChunkSection) {
|
||||||
renderWindow.rendering.executor.execute {
|
renderWindow.rendering.executor.execute {
|
||||||
renderWindow.rendering.latch.waitUntilZero() // Wait until rendering is started
|
|
||||||
try {
|
try {
|
||||||
val data = prepareChunk(chunkLocation, sectionHeight, section)
|
val data = prepareChunk(chunkLocation, sectionHeight, section)
|
||||||
val sectionMap = chunkSectionsToDraw[chunkLocation]!!
|
val sectionMap = chunkSectionsToDraw[chunkLocation]!!
|
||||||
|
@ -1,135 +1,54 @@
|
|||||||
package de.bixilon.minosoft.gui.rendering.hud
|
package de.bixilon.minosoft.gui.rendering.hud
|
||||||
|
|
||||||
import de.bixilon.minosoft.data.text.ChatComponent
|
import de.bixilon.minosoft.data.mappings.ModIdentifier
|
||||||
import de.bixilon.minosoft.gui.rendering.RenderWindow
|
import de.bixilon.minosoft.gui.rendering.RenderWindow
|
||||||
import de.bixilon.minosoft.gui.rendering.Renderer
|
import de.bixilon.minosoft.gui.rendering.Renderer
|
||||||
import de.bixilon.minosoft.gui.rendering.font.Font
|
import de.bixilon.minosoft.gui.rendering.hud.elements.HUDElement
|
||||||
import de.bixilon.minosoft.gui.rendering.font.FontBindings
|
import de.bixilon.minosoft.gui.rendering.hud.elements.text.HUDTextElement
|
||||||
import de.bixilon.minosoft.gui.rendering.shader.Shader
|
|
||||||
import de.bixilon.minosoft.gui.rendering.textures.TextureArray
|
|
||||||
import de.bixilon.minosoft.protocol.network.Connection
|
import de.bixilon.minosoft.protocol.network.Connection
|
||||||
import glm_.glm
|
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
|
||||||
import glm_.mat4x4.Mat4
|
|
||||||
import glm_.vec2.Vec2
|
|
||||||
import org.lwjgl.opengl.GL11.GL_DEPTH_TEST
|
|
||||||
import org.lwjgl.opengl.GL11.glDisable
|
|
||||||
import org.lwjgl.opengl.GL13.GL_TEXTURE0
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
|
||||||
|
|
||||||
class HUDRenderer(private val connection: Connection, private val renderWindow: RenderWindow) : Renderer {
|
class HUDRenderer(private val connection: Connection, renderWindow: RenderWindow) : Renderer {
|
||||||
private val font = Font()
|
var hudScale = HUDScale.MEDIUM
|
||||||
private val hudScale = HUDScale.MEDIUM
|
val hudElements: MutableMap<ModIdentifier, HUDElement> = mutableMapOf(
|
||||||
var fps: Int = 0
|
ModIdentifier("minosoft:hud_text_renderer") to HUDTextElement(connection, this, renderWindow),
|
||||||
var frame = 0
|
)
|
||||||
private lateinit var fontShader: Shader
|
var lastTimePrepared = 0L
|
||||||
private lateinit var fontAtlasTexture: TextureArray
|
|
||||||
private lateinit var hudMeshHUD: HUDFontMesh
|
|
||||||
private var screenWidth = 0
|
|
||||||
private var screenHeight = 0
|
|
||||||
var chatMessages: ConcurrentLinkedQueue<Pair<ChatComponent, Long>> = ConcurrentLinkedQueue()
|
|
||||||
private var showChat = true
|
|
||||||
private var showDebugScreen = true
|
|
||||||
private val fontBindingPerspectiveMatrices = mutableListOf(Mat4(), Mat4(), Mat4(), Mat4()) // according to FontBindings::ordinal
|
|
||||||
|
|
||||||
|
|
||||||
override fun init(connection: Connection) {
|
override fun init() {
|
||||||
font.load(connection.version.assetsManager)
|
for (element in hudElements.values) {
|
||||||
fontAtlasTexture = font.createAtlasTexture()
|
element.init()
|
||||||
fontAtlasTexture.load()
|
}
|
||||||
|
|
||||||
fontShader = Shader("font_vertex.glsl", "font_fragment.glsl")
|
|
||||||
fontShader.load()
|
|
||||||
hudMeshHUD = HUDFontMesh(floatArrayOf())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun drawChatComponent(position: Vec2, binding: FontBindings, text: ChatComponent, meshData: MutableList<Float>, maxSize: Vec2) {
|
override fun screenChangeResizeCallback(width: Int, height: Int) {
|
||||||
hudMeshHUD.unload()
|
for (element in hudElements.values) {
|
||||||
text.addVerticies(position, Vec2(0, 0), fontBindingPerspectiveMatrices[binding.ordinal], binding, font, hudScale, meshData, maxSize)
|
element.screenChangeResizeCallback(width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun screenChangeResizeCallback(width: Int, height: Int) {
|
|
||||||
fontShader.use()
|
|
||||||
screenWidth = width
|
|
||||||
screenHeight = height
|
|
||||||
|
|
||||||
fontBindingPerspectiveMatrices[FontBindings.LEFT_UP.ordinal] = glm.ortho(0.0f, width.toFloat(), height.toFloat(), 0.0f)
|
|
||||||
fontBindingPerspectiveMatrices[FontBindings.RIGHT_UP.ordinal] = glm.ortho(width.toFloat(), 0.0f, height.toFloat(), 0.0f)
|
|
||||||
fontBindingPerspectiveMatrices[FontBindings.RIGHT_DOWN.ordinal] = glm.ortho(width.toFloat(), 0.0f, 0.0f, height.toFloat())
|
|
||||||
fontBindingPerspectiveMatrices[FontBindings.LEFT_DOWN.ordinal] = glm.ortho(0.0f, width.toFloat(), 0.0f, height.toFloat())
|
|
||||||
|
|
||||||
prepare()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun draw() {
|
override fun draw() {
|
||||||
fontAtlasTexture.use(GL_TEXTURE0)
|
if (System.currentTimeMillis() - lastTimePrepared > ProtocolDefinition.TICK_TIME) {
|
||||||
|
|
||||||
fontShader.use()
|
|
||||||
glDisable(GL_DEPTH_TEST)
|
|
||||||
|
|
||||||
frame++
|
|
||||||
if (frame % 15 == 0) {
|
|
||||||
prepare()
|
prepare()
|
||||||
|
update()
|
||||||
|
lastTimePrepared = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
hudMeshHUD.draw()
|
for (element in hudElements.values) {
|
||||||
|
element.draw()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun prepare() {
|
fun prepare() {
|
||||||
val runtime = Runtime.getRuntime()!!
|
for (element in hudElements.values) {
|
||||||
val meshData: MutableList<Float> = mutableListOf()
|
element.prepare()
|
||||||
val componentsBindingMap: Map<FontBindings, MutableList<Any>> = mapOf(
|
|
||||||
FontBindings.LEFT_UP to mutableListOf(
|
|
||||||
"§aMinosoft (0.1-pre1)",
|
|
||||||
),
|
|
||||||
FontBindings.RIGHT_UP to mutableListOf(),
|
|
||||||
FontBindings.RIGHT_DOWN to mutableListOf(),
|
|
||||||
FontBindings.LEFT_DOWN to mutableListOf(),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (showDebugScreen) {
|
|
||||||
componentsBindingMap[FontBindings.LEFT_UP]!!.addAll(listOf(
|
|
||||||
"§fFPS: §8$fps",
|
|
||||||
"§fXYZ §8${"%.4f".format(renderWindow.camera.cameraPosition.x)} / ${"%.4f".format(renderWindow.camera.cameraPosition.y)} / ${"%.4f".format(renderWindow.camera.cameraPosition.z)}",
|
|
||||||
"§fConnected to: §8${connection.address}",
|
|
||||||
))
|
|
||||||
componentsBindingMap[FontBindings.RIGHT_UP]!!.addAll(listOf(
|
|
||||||
"§fJava: §8${Runtime.version()} ${System.getProperty("sun.arch.data.model")}bit",
|
|
||||||
"§fMemory: §8${runtime.freeMemory() * 100 / runtime.maxMemory()}% ${(runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024)}/${runtime.maxMemory() / (1024 * 1024)}MB",
|
|
||||||
"§fAllocated: §8${runtime.totalMemory() * 100 / runtime.maxMemory()}% ${runtime.totalMemory() / (1024 * 1024)}MB",
|
|
||||||
" ",
|
|
||||||
"CPU: §8${runtime.availableProcessors()}x TODO",
|
|
||||||
"OS: §8${System.getProperty("os.name")}",
|
|
||||||
" ",
|
|
||||||
"Display: §8${screenWidth}x${screenHeight}",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
if (showChat) {
|
|
||||||
for (entry in chatMessages) {
|
|
||||||
if (System.currentTimeMillis() - entry.second > 10000) {
|
|
||||||
chatMessages.remove(entry)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
componentsBindingMap[FontBindings.LEFT_DOWN]!!.add(entry.first)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for ((binding, components) in componentsBindingMap) {
|
|
||||||
val offset = Vec2(3, 3)
|
|
||||||
|
|
||||||
if (binding == FontBindings.RIGHT_DOWN || binding == FontBindings.LEFT_DOWN) {
|
|
||||||
components.reverse()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for ((_, component) in components.withIndex()) {
|
fun update() {
|
||||||
val currentOffset = Vec2()
|
for (element in hudElements.values) {
|
||||||
val chatComponent = if (component is ChatComponent) {
|
element.update()
|
||||||
component
|
|
||||||
} else {
|
|
||||||
ChatComponent.valueOf(component)
|
|
||||||
}
|
|
||||||
drawChatComponent(offset, binding, chatComponent, meshData, currentOffset)
|
|
||||||
offset += Vec2(0, currentOffset.y + 1)
|
|
||||||
}
|
|
||||||
hudMeshHUD = HUDFontMesh(meshData.toFloatArray())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package de.bixilon.minosoft.gui.rendering.hud.elements
|
||||||
|
|
||||||
|
interface HUDElement {
|
||||||
|
|
||||||
|
fun init()
|
||||||
|
fun prepare()
|
||||||
|
fun update()
|
||||||
|
fun draw()
|
||||||
|
fun screenChangeResizeCallback(width: Int, height: Int) {}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package de.bixilon.minosoft.gui.rendering.hud.elements
|
||||||
|
|
||||||
|
class RenderStats {
|
||||||
|
var fpsLastSecond = -1
|
||||||
|
var minFrameTime = Long.MAX_VALUE
|
||||||
|
var maxFrameTime = 0L
|
||||||
|
var avgFrameTime = 0L
|
||||||
|
var frames = 0L
|
||||||
|
|
||||||
|
private var lastFPSCalcTime = 0L
|
||||||
|
private var framesLastSecond = 0
|
||||||
|
|
||||||
|
private var frameStartTime = 0L
|
||||||
|
|
||||||
|
fun startFrame() {
|
||||||
|
frameStartTime = System.nanoTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun endDraw() {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun endFrame() {
|
||||||
|
val frameEndTime = System.nanoTime()
|
||||||
|
val frameTime = frameEndTime - frameStartTime
|
||||||
|
if (frameTime < minFrameTime) {
|
||||||
|
minFrameTime = frameTime
|
||||||
|
}
|
||||||
|
if (frameTime > maxFrameTime) {
|
||||||
|
maxFrameTime = frameTime
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frameEndTime - lastFPSCalcTime > 1E9) {
|
||||||
|
// 1 second
|
||||||
|
fpsLastSecond = framesLastSecond
|
||||||
|
|
||||||
|
framesLastSecond = 0
|
||||||
|
lastFPSCalcTime = frameEndTime
|
||||||
|
}
|
||||||
|
frames++
|
||||||
|
framesLastSecond++
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package de.bixilon.minosoft.gui.rendering.hud.elements.text
|
||||||
|
|
||||||
|
import de.bixilon.minosoft.data.text.ChatComponent
|
||||||
|
import de.bixilon.minosoft.gui.rendering.font.FontBindings
|
||||||
|
import de.bixilon.minosoft.modding.event.EventInvokerCallback
|
||||||
|
import de.bixilon.minosoft.modding.event.events.ChatMessageReceivingEvent
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
|
||||||
|
class HUDChatElement(hudTextElement: HUDTextElement) : HUDText {
|
||||||
|
private var showChat = true
|
||||||
|
var chatMessages: ConcurrentLinkedQueue<Pair<ChatComponent, Long>> = ConcurrentLinkedQueue()
|
||||||
|
|
||||||
|
init {
|
||||||
|
hudTextElement.connection.registerEvent(EventInvokerCallback<ChatMessageReceivingEvent> {
|
||||||
|
chatMessages.add(Pair(it.message, System.currentTimeMillis()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun prepare(chatComponents: Map<FontBindings, MutableList<Any>>) {
|
||||||
|
if (showChat) {
|
||||||
|
for (entry in chatMessages) {
|
||||||
|
if (System.currentTimeMillis() - entry.second > 10000) {
|
||||||
|
chatMessages.remove(entry)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
chatComponents[FontBindings.LEFT_DOWN]!!.add(entry.first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,113 @@
|
|||||||
|
package de.bixilon.minosoft.gui.rendering.hud.elements.text
|
||||||
|
|
||||||
|
import de.bixilon.minosoft.gui.rendering.font.FontBindings
|
||||||
|
|
||||||
|
class HUDDebugScreenElement(private val hudTextElement: HUDTextElement) : HUDText {
|
||||||
|
private val runtime = Runtime.getRuntime()
|
||||||
|
|
||||||
|
override fun prepare(chatComponents: Map<FontBindings, MutableList<Any>>) {
|
||||||
|
chatComponents[FontBindings.LEFT_UP]!!.addAll(listOf(
|
||||||
|
"§fFPS: §8${getFPS()}",
|
||||||
|
"§fTimings: §8avg ${getAvgFrameTime()}ms, min ${getMinFrameTime()}ms, max ${getMaxFrameTime()}ms",
|
||||||
|
"§fXYZ §8${getLocation()}",
|
||||||
|
"§fConnected to: §8${hudTextElement.connection.address}",
|
||||||
|
))
|
||||||
|
chatComponents[FontBindings.RIGHT_UP]!!.addAll(listOf(
|
||||||
|
"§fJava: §8${Runtime.version()} ${System.getProperty("sun.arch.data.model")}bit",
|
||||||
|
"§fMemory: §8${getUsedMemoryPercent()}% ${getFormattedUsedMemory()}/${getFormattedMaxMemory()}",
|
||||||
|
"§fAllocated: §8${getAllocatedMemoryPercent()}% ${getFormattedAllocatedMemory()}",
|
||||||
|
" ",
|
||||||
|
"CPU: §8${runtime.availableProcessors()}x TODO",
|
||||||
|
"OS: §8${System.getProperty("os.name")}",
|
||||||
|
" ",
|
||||||
|
"Display: §8${getScreenDimensions()}",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun nanoToMillis1d(nanos: Long): String {
|
||||||
|
return "%.1f".format(nanos / 1000000f)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFPS(): String {
|
||||||
|
val renderStats = hudTextElement.renderWindow.renderStats
|
||||||
|
return "${renderStats.fpsLastSecond}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAvgFrameTime(): String {
|
||||||
|
return nanoToMillis1d(hudTextElement.renderWindow.renderStats.avgFrameTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMinFrameTime(): String {
|
||||||
|
return nanoToMillis1d(hudTextElement.renderWindow.renderStats.minFrameTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMaxFrameTime(): String {
|
||||||
|
return nanoToMillis1d(hudTextElement.renderWindow.renderStats.maxFrameTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun getUsedMemory(): Long {
|
||||||
|
return runtime.totalMemory() - runtime.freeMemory()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFormattedUsedMemory(): String {
|
||||||
|
return formatBytes(getUsedMemory())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAllocatedMemory(): Long {
|
||||||
|
return runtime.totalMemory()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFormattedAllocatedMemory(): String {
|
||||||
|
return formatBytes(getAllocatedMemory())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMaxMemory(): Long {
|
||||||
|
return runtime.maxMemory()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFormattedMaxMemory(): String {
|
||||||
|
return formatBytes(getMaxMemory())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getUsedMemoryPercent(): Long {
|
||||||
|
return getUsedMemory() * 100 / runtime.maxMemory()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAllocatedMemoryPercent(): Long {
|
||||||
|
return getAllocatedMemory() * 100 / runtime.maxMemory()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLocation(): String {
|
||||||
|
val cameraPosition = hudTextElement.renderWindow.camera.cameraPosition
|
||||||
|
return "${formatCoordinate(cameraPosition.x)} / ${formatCoordinate(cameraPosition.y)} / ${formatCoordinate(cameraPosition.z)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getScreenDimensions(): String {
|
||||||
|
return "${hudTextElement.renderWindow.screenWidth}x${hudTextElement.renderWindow.screenHeight}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val UNITS = listOf("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB")
|
||||||
|
fun formatBytes(bytes: Long): String {
|
||||||
|
var lastFactor = 1L
|
||||||
|
var currentFactor = 1024L
|
||||||
|
for (unit in UNITS) {
|
||||||
|
if (bytes < currentFactor) {
|
||||||
|
if (bytes < (lastFactor * 10)) {
|
||||||
|
return "${"%.1f".format(bytes / lastFactor.toFloat())}${unit}"
|
||||||
|
}
|
||||||
|
return "${bytes / lastFactor}${unit}"
|
||||||
|
}
|
||||||
|
lastFactor = currentFactor
|
||||||
|
currentFactor *= 1024L
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun formatCoordinate(coordinate: Float): String {
|
||||||
|
return "%.4f".format(coordinate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package de.bixilon.minosoft.gui.rendering.hud
|
package de.bixilon.minosoft.gui.rendering.hud.elements.text
|
||||||
|
|
||||||
import glm_.BYTES
|
import glm_.BYTES
|
||||||
import org.lwjgl.opengl.GL11.GL_FLOAT
|
import org.lwjgl.opengl.GL11.GL_FLOAT
|
@ -0,0 +1,10 @@
|
|||||||
|
package de.bixilon.minosoft.gui.rendering.hud.elements.text
|
||||||
|
|
||||||
|
import de.bixilon.minosoft.gui.rendering.font.FontBindings
|
||||||
|
|
||||||
|
interface HUDText {
|
||||||
|
|
||||||
|
fun prepare(chatComponents: Map<FontBindings, MutableList<Any>>)
|
||||||
|
fun update() {}
|
||||||
|
fun draw() {}
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
package de.bixilon.minosoft.gui.rendering.hud.elements.text
|
||||||
|
|
||||||
|
import de.bixilon.minosoft.data.mappings.ModIdentifier
|
||||||
|
import de.bixilon.minosoft.data.text.ChatComponent
|
||||||
|
import de.bixilon.minosoft.gui.rendering.RenderWindow
|
||||||
|
import de.bixilon.minosoft.gui.rendering.font.Font
|
||||||
|
import de.bixilon.minosoft.gui.rendering.font.FontBindings
|
||||||
|
import de.bixilon.minosoft.gui.rendering.hud.HUDRenderer
|
||||||
|
import de.bixilon.minosoft.gui.rendering.hud.elements.HUDElement
|
||||||
|
import de.bixilon.minosoft.gui.rendering.shader.Shader
|
||||||
|
import de.bixilon.minosoft.gui.rendering.textures.TextureArray
|
||||||
|
import de.bixilon.minosoft.protocol.network.Connection
|
||||||
|
import glm_.glm
|
||||||
|
import glm_.mat4x4.Mat4
|
||||||
|
import glm_.vec2.Vec2
|
||||||
|
import org.lwjgl.opengl.GL11.GL_DEPTH_TEST
|
||||||
|
import org.lwjgl.opengl.GL11.glDisable
|
||||||
|
import org.lwjgl.opengl.GL13.GL_TEXTURE0
|
||||||
|
|
||||||
|
class HUDTextElement(val connection: Connection, val hudRenderer: HUDRenderer, val renderWindow: RenderWindow) : HUDElement {
|
||||||
|
private val fontBindingPerspectiveMatrices = mutableListOf(Mat4(), Mat4(), Mat4(), Mat4()) // according to FontBindings::ordinal
|
||||||
|
private lateinit var fontShader: Shader
|
||||||
|
private lateinit var hudMeshHUD: HUDFontMesh
|
||||||
|
private lateinit var fontAtlasTexture: TextureArray
|
||||||
|
private val font = Font()
|
||||||
|
private lateinit var componentsBindingMap: Map<FontBindings, MutableList<Any>>
|
||||||
|
|
||||||
|
var hudTextElements: MutableMap<ModIdentifier, HUDText> = mutableMapOf(
|
||||||
|
ModIdentifier("minosoft:debug_screen") to HUDDebugScreenElement(this),
|
||||||
|
ModIdentifier("minosoft:chat") to HUDChatElement(this),
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun screenChangeResizeCallback(width: Int, height: Int) {
|
||||||
|
fontShader.use()
|
||||||
|
|
||||||
|
fontBindingPerspectiveMatrices[FontBindings.LEFT_UP.ordinal] = glm.ortho(0.0f, width.toFloat(), height.toFloat(), 0.0f)
|
||||||
|
fontBindingPerspectiveMatrices[FontBindings.RIGHT_UP.ordinal] = glm.ortho(width.toFloat(), 0.0f, height.toFloat(), 0.0f)
|
||||||
|
fontBindingPerspectiveMatrices[FontBindings.RIGHT_DOWN.ordinal] = glm.ortho(width.toFloat(), 0.0f, 0.0f, height.toFloat())
|
||||||
|
fontBindingPerspectiveMatrices[FontBindings.LEFT_DOWN.ordinal] = glm.ortho(0.0f, width.toFloat(), 0.0f, height.toFloat())
|
||||||
|
|
||||||
|
prepare()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun drawChatComponent(position: Vec2, binding: FontBindings, text: ChatComponent, meshData: MutableList<Float>, maxSize: Vec2) {
|
||||||
|
hudMeshHUD.unload()
|
||||||
|
text.addVerticies(position, Vec2(0, 0), fontBindingPerspectiveMatrices[binding.ordinal], binding, font, hudRenderer.hudScale, meshData, maxSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun prepare() {
|
||||||
|
componentsBindingMap = mapOf(
|
||||||
|
FontBindings.LEFT_UP to mutableListOf(
|
||||||
|
"§aMinosoft (0.1-pre1)",
|
||||||
|
),
|
||||||
|
FontBindings.RIGHT_UP to mutableListOf(),
|
||||||
|
FontBindings.RIGHT_DOWN to mutableListOf(),
|
||||||
|
FontBindings.LEFT_DOWN to mutableListOf(),
|
||||||
|
)
|
||||||
|
for (hudTextElement in hudTextElements.values) {
|
||||||
|
hudTextElement.prepare(componentsBindingMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update() {
|
||||||
|
for (hudTextElement in hudTextElements.values) {
|
||||||
|
hudTextElement.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
val meshData: MutableList<Float> = mutableListOf()
|
||||||
|
|
||||||
|
for ((binding, components) in componentsBindingMap) {
|
||||||
|
val offset = Vec2(3, 3)
|
||||||
|
|
||||||
|
if (binding == FontBindings.RIGHT_DOWN || binding == FontBindings.LEFT_DOWN) {
|
||||||
|
components.reverse()
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((_, component) in components.withIndex()) {
|
||||||
|
val currentOffset = Vec2()
|
||||||
|
drawChatComponent(offset, binding, ChatComponent.valueOf(component), meshData, currentOffset)
|
||||||
|
offset += Vec2(0, currentOffset.y + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hudMeshHUD.unload()
|
||||||
|
hudMeshHUD = HUDFontMesh(meshData.toFloatArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun init() {
|
||||||
|
font.load(connection.version.assetsManager)
|
||||||
|
fontShader = Shader("font_vertex.glsl", "font_fragment.glsl")
|
||||||
|
fontShader.load()
|
||||||
|
hudMeshHUD = HUDFontMesh(floatArrayOf())
|
||||||
|
|
||||||
|
|
||||||
|
fontAtlasTexture = font.createAtlasTexture()
|
||||||
|
fontAtlasTexture.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun draw() {
|
||||||
|
fontAtlasTexture.use(GL_TEXTURE0)
|
||||||
|
fontShader.use()
|
||||||
|
glDisable(GL_DEPTH_TEST)
|
||||||
|
|
||||||
|
for (hudTextElement in hudTextElements.values) {
|
||||||
|
hudTextElement.draw()
|
||||||
|
}
|
||||||
|
hudMeshHUD.draw()
|
||||||
|
}
|
||||||
|
}
|
@ -135,11 +135,18 @@ public class Connection {
|
|||||||
version.load(latch); // ToDo: show gui loader
|
version.load(latch); // ToDo: show gui loader
|
||||||
this.customMapping.setVersion(version);
|
this.customMapping.setVersion(version);
|
||||||
this.customMapping.setParentMapping(version.getMapping());
|
this.customMapping.setParentMapping(version.getMapping());
|
||||||
|
|
||||||
|
if (!StaticConfiguration.HEADLESS_MODE) {
|
||||||
|
this.rendering = new Rendering(this);
|
||||||
|
this.rendering.start(latch);
|
||||||
|
}
|
||||||
|
latch.waitForChange();
|
||||||
Log.info(String.format("Connecting to server: %s", address));
|
Log.info(String.format("Connecting to server: %s", address));
|
||||||
this.network.connect(address);
|
this.network.connect(address);
|
||||||
|
latch.countDown();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.printException(e, LogLevels.DEBUG);
|
Log.printException(e, LogLevels.DEBUG);
|
||||||
Log.fatal(String.format("Could not load mapping for %s. This version seems to be unsupported!", version));
|
Log.fatal(String.format("Could not load version %s. This version seems to be unsupported!", version));
|
||||||
this.lastException = new MappingsLoadingException("Mappings could not be loaded", e);
|
this.lastException = new MappingsLoadingException("Mappings could not be loaded", e);
|
||||||
setConnectionState(ConnectionStates.FAILED_NO_RETRY);
|
setConnectionState(ConnectionStates.FAILED_NO_RETRY);
|
||||||
}
|
}
|
||||||
@ -165,7 +172,6 @@ public class Connection {
|
|||||||
this.version = version;
|
this.version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void handle(ClientboundPacket p) {
|
public void handle(ClientboundPacket p) {
|
||||||
this.handlingQueue.add(p);
|
this.handlingQueue.add(p);
|
||||||
}
|
}
|
||||||
@ -372,10 +378,6 @@ public class Connection {
|
|||||||
case FAILED_NO_RETRY -> handlePingCallbacks(null);
|
case FAILED_NO_RETRY -> handlePingCallbacks(null);
|
||||||
case PLAY -> {
|
case PLAY -> {
|
||||||
Minosoft.CONNECTIONS.put(getConnectionId(), this);
|
Minosoft.CONNECTIONS.put(getConnectionId(), this);
|
||||||
if (!StaticConfiguration.HEADLESS_MODE) {
|
|
||||||
this.rendering = new Rendering(this);
|
|
||||||
this.rendering.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CLI.getCurrentConnection() == null) {
|
if (CLI.getCurrentConnection() == null) {
|
||||||
CLI.setCurrentConnection(this);
|
CLI.setCurrentConnection(this);
|
||||||
|
@ -56,8 +56,6 @@ public class PacketChatMessageReceiving extends ClientboundPacket {
|
|||||||
case ABOVE_HOTBAR -> "[HOTBAR] ";
|
case ABOVE_HOTBAR -> "[HOTBAR] ";
|
||||||
default -> "[CHAT] ";
|
default -> "[CHAT] ";
|
||||||
} + event.getMessage());
|
} + event.getMessage());
|
||||||
|
|
||||||
connection.getRenderer().getRenderWindow().getHudRenderer().getChatMessages().add(new kotlin.Pair<>(this.message, System.currentTimeMillis()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -82,6 +82,10 @@ public final class ProtocolDefinition {
|
|||||||
|
|
||||||
public static final char[] OBFUSCATED_CHARS = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~".toCharArray();
|
public static final char[] OBFUSCATED_CHARS = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~".toCharArray();
|
||||||
|
|
||||||
|
|
||||||
|
public static final int TICKS_PER_SECOND = 20;
|
||||||
|
public static final int TICK_TIME = 1000 / TICKS_PER_SECOND;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// java does (why ever) not allow to directly assign a null
|
// java does (why ever) not allow to directly assign a null
|
||||||
InetAddress tempInetAddress;
|
InetAddress tempInetAddress;
|
||||||
|
@ -32,6 +32,14 @@ public class CountUpAndDownLatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void waitUntilZero(long timeout) throws InterruptedException {
|
||||||
|
synchronized (this.lock) {
|
||||||
|
while (this.count > 0) {
|
||||||
|
this.lock.wait(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void countUp() {
|
public void countUp() {
|
||||||
synchronized (this.lock) {
|
synchronized (this.lock) {
|
||||||
this.total++;
|
this.total++;
|
||||||
@ -41,6 +49,9 @@ public class CountUpAndDownLatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void countDown() {
|
public void countDown() {
|
||||||
|
if (this.count == 0) {
|
||||||
|
throw new IllegalStateException("Can not count down, counter is already 0");
|
||||||
|
}
|
||||||
synchronized (this.lock) {
|
synchronized (this.lock) {
|
||||||
this.count--;
|
this.count--;
|
||||||
this.lock.notifyAll();
|
this.lock.notifyAll();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user