11package gay.`object`.mlogv32
22
3+ import arc.Core
34import arc.files.Fi
4- import arc.util.Timer
5+ import arc.util.Log
6+ import io.ktor.network.selector.*
7+ import io.ktor.network.sockets.*
8+ import io.ktor.utils.io.*
9+ import kotlinx.coroutines.*
10+ import kotlinx.coroutines.CancellationException
11+ import kotlinx.serialization.SerialName
12+ import kotlinx.serialization.Serializable
13+ import kotlinx.serialization.json.Json
514import mindustry.Vars
615import mindustry.gen.Building
716import mindustry.world.blocks.logic.LogicBlock.LogicBuild
817import mindustry.world.blocks.logic.SwitchBlock.SwitchBuild
18+ import kotlin.concurrent.thread
19+ import kotlin.coroutines.resume
20+ import kotlin.coroutines.resumeWithException
921
10- data class ProcessorAccess (
22+ class ProcessorAccess (
1123 val build : LogicBuild ,
1224 val memoryX : Int ,
1325 val memoryY : Int ,
@@ -70,6 +82,41 @@ data class ProcessorAccess(
7082 writes.close()
7183 }
7284
85+ fun isServerRunning () = serverThread != null && serverJob != null && serverBuildId == build.id
86+
87+ fun startServer (hostname : String , port : Int ) {
88+ if (serverThread != null ) stopServer()
89+
90+ Log .info(" Starting ProcessorAccess socket server at $hostname :$port for building ${build.id} ..." )
91+
92+ serverBuildId = build.id
93+ serverThread = thread(isDaemon = true ) {
94+ runBlocking {
95+ serverJob = launch {
96+ try {
97+ val selector = SelectorManager (Dispatchers .IO )
98+ aSocket(selector).tcp().bind(hostname, port).use { serverSocket ->
99+ runServer(serverSocket)
100+ }
101+ } catch (e: Exception ) {
102+ Log .err(" ProcessorAccess server failed" , e)
103+ }
104+ }
105+ }
106+
107+ // cleanup
108+ serverJob = null
109+ serverThread = null
110+ serverBuildId = null
111+
112+ Log .info(" ProcessorAccess thread exiting." )
113+ }
114+ }
115+
116+ fun stopServer () {
117+ ProcessorAccess .stopServer()
118+ }
119+
73120 private fun ramWordsSequence (startAddress : Int = 0) = sequence {
74121 require(startAddress in ramStart.. < ramEnd) { " Start address must be within RAM." }
75122 require(startAddress.mod(4 ) == 0 ) { " Start address must be aligned to 4 bytes." }
@@ -133,7 +180,40 @@ data class ProcessorAccess(
133180 return proc
134181 }
135182
183+ private suspend fun runServer (serverSocket : ServerSocket ) {
184+ while (true ) {
185+ Log .info(" Waiting for clients..." )
186+ serverSocket.accept().use { client ->
187+ Log .info(" Client connected!" )
188+ val rx = client.openReadChannel()
189+ val tx = client.openWriteChannel(true )
190+ while (true ) {
191+ val response: Response = try {
192+ val line = rx.readUTF8Line() ? : break
193+ Log .info(" Got request: $line " )
194+ val request = Json .decodeFromString<Request >(line)
195+ request.handle(this )
196+ } catch (e: CancellationException ) {
197+ throw e
198+ } catch (e: IllegalArgumentException ) {
199+ Log .err(" Bad request" , e)
200+ ErrorResponse .badRequest(e)
201+ } catch (e: Exception ) {
202+ Log .err(" Request failed" , e)
203+ ErrorResponse (e)
204+ }
205+ tx.writeStringUtf8(Json .encodeToString(response) + " \n " )
206+ }
207+ Log .info(" Client disconnected." )
208+ }
209+ }
210+ }
211+
136212 companion object {
213+ private var serverThread: Thread ? = null
214+ private var serverJob: Job ? = null
215+ private var serverBuildId: Int? = null
216+
137217 fun of (build : LogicBuild ): ProcessorAccess ? {
138218 return ProcessorAccess (
139219 build,
@@ -150,6 +230,14 @@ data class ProcessorAccess(
150230 resetSwitch = buildVar<SwitchBuild >(build, " RESET_SWITCH" ) ? : return null ,
151231 )
152232 }
233+
234+ fun stopServer () {
235+ if (serverThread == null ) return
236+ Log .info(" Stopping ProcessorAccess server for building $serverBuildId ..." )
237+ serverJob?.cancel()
238+ serverThread?.join()
239+ Log .info(" Stopped ProcessorAccess server." )
240+ }
153241 }
154242}
155243
@@ -171,3 +259,91 @@ private fun positiveIntVar(build: LogicBuild, name: String): Int? =
171259
172260private inline fun <reified T : Building > buildVar (build : LogicBuild , name : String ): T ? =
173261 build.executor.optionalVar(name)?.obj() as ? T
262+
263+ @Serializable
264+ sealed class Request {
265+ abstract suspend fun handle (processor : ProcessorAccess ): Response
266+
267+ protected suspend fun <T > runOnMainThread (block : () -> T ): T {
268+ return suspendCancellableCoroutine { continuation ->
269+ Core .app.post {
270+ try {
271+ continuation.resume(block())
272+ } catch (e: Exception ) {
273+ continuation.resumeWithException(e)
274+ }
275+ }
276+ }
277+ }
278+ }
279+
280+ @Serializable
281+ @SerialName(" flash" )
282+ data class FlashRequest (val path : String ) : Request() {
283+ override suspend fun handle (processor : ProcessorAccess ) = runOnMainThread {
284+ val file = Core .files.absolute(path)
285+ require(file.exists()) { " File not found." }
286+
287+ val bytes = processor.flashRom(file)
288+ SuccessResponse (" Successfully flashed $bytes bytes from $file to ROM." )
289+ }
290+ }
291+
292+ @Serializable
293+ @SerialName(" dump" )
294+ data class DumpRequest (val path : String ) : Request() {
295+ override suspend fun handle (processor : ProcessorAccess ) = runOnMainThread {
296+ val file = Core .files.absolute(path)
297+ file.parent().mkdirs()
298+
299+ val bytes = processor.dumpRam(file)
300+ SuccessResponse (" Successfully dumped $bytes bytes from RAM to $file ." )
301+ }
302+ }
303+
304+ @Serializable
305+ @SerialName(" start" )
306+ data class StartRequest (val wait : Boolean ) : Request() {
307+ override suspend fun handle (processor : ProcessorAccess ): Response {
308+ runOnMainThread {
309+ processor.resetSwitch.configure(false )
310+ }
311+ if (! wait) {
312+ return SuccessResponse (" Processor started." )
313+ }
314+
315+ while (true ) {
316+ delay(500 )
317+ val stopped = runOnMainThread { processor.resetSwitch.enabled }
318+ if (stopped) {
319+ return SuccessResponse (" Processor has halted." )
320+ }
321+ }
322+ }
323+ }
324+
325+ @Serializable
326+ @SerialName(" stop" )
327+ data object StopRequest : Request () {
328+ override suspend fun handle (processor : ProcessorAccess ) = runOnMainThread {
329+ processor.resetSwitch.configure(true )
330+ SuccessResponse (" Processor stopped." )
331+ }
332+ }
333+
334+ @Serializable
335+ sealed class Response
336+
337+ @Serializable
338+ @SerialName(" success" )
339+ data class SuccessResponse (val message : String ) : Response()
340+
341+ @Serializable
342+ @SerialName(" error" )
343+ data class ErrorResponse (val message : String ) : Response() {
344+ constructor (e: Exception ) : this (" Request failed: $e " )
345+
346+ companion object {
347+ fun badRequest (e : IllegalArgumentException ) = ErrorResponse (" Bad request: $e " )
348+ }
349+ }
0 commit comments