app/src/main/java/puscas/mobilertapp/RenderTask.kt
package puscas.mobilertapp import android.graphics.Bitmapimport android.os.Debugimport android.os.SystemClockimport android.widget.Buttonimport android.widget.TextViewimport com.google.common.base.Preconditionsimport puscas.mobilertapp.configs.ConfigRenderTaskimport puscas.mobilertapp.constants.Constantsimport puscas.mobilertapp.constants.ConstantsMethodsimport puscas.mobilertapp.constants.ConstantsRendererimport puscas.mobilertapp.constants.ConstantsUIimport puscas.mobilertapp.constants.Stateimport puscas.mobilertapp.utils.AsyncTaskCoroutineimport puscas.mobilertapp.utils.Utilsimport java.math.RoundingModeimport java.text.NumberFormatimport java.util.Localeimport java.util.concurrent.ExecutorServiceimport java.util.concurrent.Executorsimport java.util.concurrent.TimeUnitimport java.util.logging.Logger /** * An asynchronous task to render a frame and update the [TextView] text. * At the end of the task, it sets the render [Button] to "Render". * * @property requestRender A [Runnable] to the [DrawView.requestRender] method which is used in the [RenderTask.timer]. * @property finishRender A [Runnable] method which stops the Ray Tracer engine and sets the [RenderTask.stateT] to [State.IDLE]. * @property updateInterval The interval in `TimeUnit.MILLISECONDS` between each call to the [RenderTask.timer] [Runnable]. * @property primitivesT The number of primitives and lights in the scene. * @property resolutionT The width and height of the [Bitmap] where the Ray Tracer engine is rendering the scene. * @property threadsT The number of threads in the Ray Tracer engine. * @property samplesPixelT The selected number of samples per pixel. * @property samplesLightT The selected number of samples per light. * @property textView The [TextView] which outputs debug information about the Ray Tracer engine like the current rendering time and the fps. * @property buttonRender The [Button] which starts and stops the rendering process. This is needed in order to change its text at the end of the rendering process. */class RenderTask private constructor( private val requestRender : Runnable, private val finishRender: Runnable, private val updateInterval: Long, private val primitivesT: String, private val resolutionT: String, private val threadsT: String, private val samplesPixelT: String, private val samplesLightT: String, private val textView: TextView, private val buttonRender: Button,) : AsyncTaskCoroutine() { /** * Logger for this class. */ private val logger = Logger.getLogger(RenderTask::class.java.simpleName) /** * The number of milliseconds in a second. */ private val millisecondsInSecond = 1000.0f /** * An [ExecutorService] which schedules every * [RenderTask.updateInterval] `TimeUnit.MILLISECONDS` the * [RenderTask.timer] [Runnable]. */ private val executorService = Executors.newScheduledThreadPool(ConstantsRenderer.NUMBER_THREADS) /** * A [Runnable] which updates the text to print in the [TextView]. */ private val timer: Runnable /** * The timestamp of the start rendering process. */ private val startTimeStamp: Long /** * The number of times that the [RenderTask.timer] is called. */ private var frame = 0 /** * The timestamp at the start of every second. */ private var timebase = 0.0f /** * The number of times that the [RenderTask.timer] was called in a * second. */ private var fps = 0.0f /** * The current Ray Tracer engine [State]. */ private var stateT: String? = null /** * The frames per second of the Ray Tracer engine. */ private var fpsT: String? = null /** * The time, in seconds, spent constructing the Ray Tracer renderer. */ private var timeFrameT: String? = null /** * The current time, in seconds, that the Ray Tracer engine spent rendering * a scene. */ private var timeT: String? = null /** * A [String] containing the [RenderTask.fps] in order to print * it in the [RenderTask.textView]. */ private var fpsRenderT: String? = null /** * The amount of allocated memory in the native heap (in MegaBytes). */ private var allocatedT: String? = null /** * The current sample for all the pixels. */ private var sampleT: String? = null /** * The [NumberFormat] to use when printing the [Float] values in the [TextView]. */ private val formatter = NumberFormat.getInstance(Locale.US) /** * A private constructor of this class to force using the * RenderTask builder. */ init { logger.info("RenderTask") formatter.maximumFractionDigits = 2 formatter.minimumFractionDigits = 2 formatter.roundingMode = RoundingMode.HALF_UP startTimeStamp = SystemClock.elapsedRealtime() resetTextStats() timer = Runnable { logger.info(ConstantsMethods.TIMER) updateFps() updateTextStats() val currentState = State.entries[rtGetState()] stateT = currentState.toString() requestRender.run() publishProgressAsync() if (currentState != State.BUSY) { stopTask() } logger.info(ConstantsMethods.TIMER + ConstantsMethods.FINISHED) } checksArguments() } /** * Helper method that updates some statistics in the fields of this class * that will be presented in the [TextView]. */ private fun updateTextStats() { fpsT = "fps:" + formatter.format(rtGetFps().toDouble()) fpsRenderT = "[" + formatter.format(fps.toDouble()) + "]" timeFrameT = ",t:" + formatter.format(rtGetTimeRenderer().toDouble() / millisecondsInSecond.toDouble()) val currentTime = SystemClock.elapsedRealtime() timeT = "[" + formatter.format( (currentTime - startTimeStamp).toDouble() / millisecondsInSecond.toDouble() ) + "]" allocatedT = ",m:" + (Debug.getNativeHeapAllocatedSize() / Constants.BYTES_IN_MEGABYTE) + "mb" sampleT = "," + rtGetSample() } /** * Helper method that resets some statistics in the fields of this class * that will be presented in the [TextView]. */ private fun resetTextStats() { fpsT = "fps:" + formatter.format(0.0) fpsRenderT = "[" + formatter.format(0.0) + "]" timeFrameT = ",t:" + formatter.format(0.0) timeT = "[" + formatter.format(0.0) + "]" stateT = " " + State.IDLE.id allocatedT = ",m:" + (Debug.getNativeHeapAllocatedSize() / Constants.BYTES_IN_MEGABYTE) + "mb" sampleT = ",0" } /** * Helper method that validates the fields of this class. */ private fun checksArguments() { Preconditions.checkNotNull(requestRender, "requestRender shouldn't be null") Preconditions.checkNotNull(finishRender, "finishRender shouldn't be null") Preconditions.checkNotNull(textView, "textView shouldn't be null") Preconditions.checkNotNull(buttonRender, "buttonRender shouldn't be null") } /** * Gets the number of frames per second which the Ray Tracer engine could * render the scene. * * @return The number of frames per second. */ private external fun rtGetFps(): Float /** * Gets the time, in milliseconds, spent constructing the Ray Tracer * renderer. * * @return The time spent constructing the Ray Tracer renderer. */ private external fun rtGetTimeRenderer(): Long /** * Gets the current sample for all the pixels. * * @return The current sample for all the pixels. */ private external fun rtGetSample(): Int /** * Gets an `int` which represents the current Ray Tracer engine * [State]. * * @return The current Ray Tracer engine [State]. */ external fun rtGetState(): Int /** * Auxiliary method which calculates the number of times * [RenderTask.timer] was called and in each second. */ private fun updateFps() { frame++ val time = SystemClock.elapsedRealtime().toFloat() fps = frame * millisecondsInSecond / (time - timebase) if (time - timebase > millisecondsInSecond) { timebase = time frame = 0 } } /** * Auxiliary method which sets the current debug information in the * [RenderTask.textView]. */ private fun printText() { val aux = (fpsT + fpsRenderT + resolutionT + threadsT + samplesPixelT + samplesLightT + sampleT + ConstantsUI.LINE_SEPARATOR + stateT + allocatedT + timeFrameT + timeT + primitivesT) textView.text = aux } /** * Helper method which waits for the [executorService] to finish. */ override fun waitForTaskToFinish() { logger.info("waitForTaskToFinish") Utils.waitExecutorToFinish(this.executorService) logger.info("waitForTaskToFinish finished") } /** * Helper method which stops the [AsyncTaskCoroutine]. */ override fun stopTask() { logger.info("stopTask") this.executorService.shutdown() logger.info("stopTask finished") } override fun onPreExecute() { logger.info("onPreExecute") } override fun doInBackground() { logger.info("doInBackground") try { this.executorService.scheduleAtFixedRate( this.timer, 0L, this.updateInterval, TimeUnit.MILLISECONDS ) } catch (ex: Throwable) { logger.severe("doInBackground failed") return } waitForTaskToFinish() val message = "doInBackground" + ConstantsMethods.FINISHED logger.info(message) } override fun onProgressUpdate() { this.logger.info("onProgressUpdate") printText() val message = "onProgressUpdate" + ConstantsMethods.FINISHED this.logger.info(message) } override fun onPostExecute() { this.logger.info("onPostExecute") printText() this.requestRender.run() stopTask() MainActivity.resetErrno() this.finishRender.run() this.buttonRender.setText(R.string.render) val message = "onPostExecute" + ConstantsMethods.FINISHED this.logger.info(message) } class Builder private constructor() { lateinit var config : ConfigRenderTask companion object { fun create() = Builder() } fun build() = RenderTask( config.requestRender, config.finishRender, config.updateInterval, ",p=" + config.numPrimitives + ",l=" + config.numLights, ",r:" + config.resolution.width + 'x' + config.resolution.height, ",t:" + config.numThreads, ",spp:" + config.samples.samplesPixel, ",spl:" + config.samples.samplesLight, config.textView, config.buttonRender, ) }}