openrndr-guide icon indicating copy to clipboard operation
openrndr-guide copied to clipboard

Adding chapter "Advanced Topics/Custom Programs"

Open Treide1 opened this issue 2 years ago • 10 comments

This PR adds the chapter 11_Advanced_Topics/C135_Custom_programs.kt The chapter explains how to inherit from ProgramImplementation to write a custom program that adds to the scope of the normal program { ... } block.

This is useful for more experienced OPENRNDR users, that want to reuse code with minimal effort.

Treide1 avatar Jun 08 '23 11:06 Treide1

Thank you! I will review this today :-)

hamoid avatar Jun 12 '23 11:06 hamoid

Finally I got into trying how this look like:

image

image

hamoid avatar Jun 13 '23 21:06 hamoid

Thank you very much for your contribution! Super nice to have one more person writing!!!

I think I could tweak some sentences so they match the tone of other parts of the guide, but before getting into that... I was wondering if you are aware of ApplicationPreload?

I wonder if that is a simpler approach to achieve part of what you can do with your approach (which may be more flexible, but also a bit more verbose).

An example I use to add screenshots, video recording and ESC=quit to all sketches:

package org.openrndr

import org.openrndr.extensions.Screenshots
import org.openrndr.ffmpeg.ScreenRecorder

/**
 * Preload class to inject functionality into all sketches
 */

class Preload : ApplicationPreload() {
//    override fun onConfiguration(configuration: Configuration) {
//        configuration.width = 1200
//        configuration.height = 600
//    }

    override fun onProgramSetup(program: Program) {
        val screenRecorder = ScreenRecorder().apply {
            outputToVideo = false
            frameClock = false
        }
        program.extend(screenRecorder)
        program.extend(Screenshots())
        program.keyboard.keyDown.listen {
            when {
                it.key == KEY_ESCAPE -> program.application.exit()
                it.name == "v" && it.modifiers.isEmpty() -> {
                    screenRecorder.outputToVideo = !screenRecorder.outputToVideo
                    program.application.configuration.vsync =
                        !screenRecorder.outputToVideo
                    println("ScreenRecorder: ${if (screenRecorder.outputToVideo) "ON" else "OFF"}")
                }
            }
        }
    }
}

Do you think it's worth including both approaches in the guide?

hamoid avatar Jun 13 '23 21:06 hamoid

In https://github.com/openrndr/openrndr-guide/wiki we had planned a section for reusing code. Maybe both approaches could be listed there, together with others like creating a library, or sharing a source folder across multiple projects. What do you think?

hamoid avatar Jun 13 '23 21:06 hamoid

Hi Abe, I was not aware of ApplicationPreload and couldn't figure out how to use one's custom subclass of that.

The guide page should be adjusted for sure :D Feel free to change the wording and some of the weird formating.

Treide1 avatar Jun 14 '23 09:06 Treide1

Hi! One actually doesn't need to subclass, but just put that class (the example I shared) in a file, for example in src/main/kotlin/org/openrndr/Preload.kt. When a program starts it searches for ApplicationPreload instances and uses them if found. You can see it mentioned in the log in Idea when running any program.

If @edwinRNDR agrees, I think I could do this:

  1. merge this as it is.
  2. do a tiny bit of clean up / wording.
  3. next, move it into a "reusing code" section, where we can mention:
  • How to use ApplicationPreload (used by all sketches of one project)
  • How to extend Program (your example) (available to sketches of one project)
  • How to use https://github.com/edwinRNDR/openrndr-library-template (reuse code across projects)
  • ~~How to include an external src/ folder (reuse code across projects)~~

hamoid avatar Jun 14 '23 09:06 hamoid

In https://github.com/openrndr/openrndr-guide/wiki we had planned a section for reusing code. Maybe both approaches could be listed there, together with others like creating a library, or sharing a source folder across multiple projects. What do you think?

I see the following situation:

  • People start OPENRNDR and write code they (eventually) use over and over
  • They find the page of the guide discussing code reuse
  • They move their common parts (declarations, executed code) into the reusable part
  • They probably want to change their existing code to now rely on the reusable
    • This should take little effort (in the case of myProgram a change of one outer function name and deleting moved code)
    • This should be side effect free to other projects

-> Future projects:

  • A decision is made which common part should be used: none (standard program), reusable (myProgram) or maybe even another reusable (myVideoBasedProgram, myShapeBasedProgram)

Depending on how experimental/stable the code is and if the intention is more for personal use/community use of the written code, I would recommend either a ProgramImplementation or writing it as a library.

In my opinion, more options would be more confusing than helpful.

Treide1 avatar Jun 14 '23 09:06 Treide1

That's a good point. More options can be confusing.

It's tricky because each option has its benefits.

shared src folder vs library-template

I think including a src folder with shared code in a project is very easy to understand and implement by anyone. It may be better to use the library-template, but it has a drawback: to edit the code one needs to open a separate project, change the code, then publish to maven local (instead of just editing the code inside the open project). Benefits: is that it is easier to share with others via jitpack and build times are better. I'm ok with not showing how to use the shared src folder.

ApplicationPreload vs extending Program

Extending Program is more flexible, but the code can be confusing for people not experienced with Kotlin. Also, it requires more code than using ApplicationPreload. For example, to make all programs quit on ESC,

package org.openrndr

class Preload : ApplicationPreload() {
    override fun onProgramSetup(program: Program) {
        program.keyboard.keyDown.listen {
            if(it.key == KEY_ESCAPE) program.application.exit()
        }
    }
}

vs something like this:

class MyProgram(private val init: suspend MyProgram.() -> Unit) : ProgramImplementation(suspend = false) {
    override suspend fun setup() {
        super.setup()
        init()
        keyboard.keyDown.listen {
            if(it.key == KEY_ESCAPE) program.application.exit()
        }
    }
}
fun ApplicationBuilder.myProgram(
    init: suspend MyProgram.() -> Unit
): MyProgram {
    program = MyProgram(init)
    return program as MyProgram
}

It is very nice though that with the second approach you can easily choose if you want to use program or myProgram. But even after 3 years there are still parts of this code I'm not familiar with.

That's why I would go for showing both alternatives and suggest using ApplicationPreload until you are comfortable with the more advanced approach.

I'm concerned that by suggesting code that is harder to understand it may scare some people away, so it's nice to provide a simpler solution as well.

What do you think?

hamoid avatar Jun 14 '23 10:06 hamoid

I was not thinking for the people unfamiliar with Kotlin. As an Android Dev, I am maybe too used to it :D Showing how to use Preload upfront is the better choice.

It also makes sense as this allows for reuse within one IntelliJ project, which is the logical unit of project/bundle of apps. And also goes well with binding a src folder.

My only concern with the Preload class is that it enforces a org/openrndr/ path. That feels a bad odd, as specifically this part of your project has the same package name as the library you are using ... but isn't that library 🤔

I would encourage creating a library only in the context of orx/orml - this way people are more keen on contributing back if they have something worthy of a lib

Treide1 avatar Jun 14 '23 18:06 Treide1

Thanks for the feedback :) I agree with your concern regarding the location of the Preload file. I guess one could place it anywhere, but it would complain about package not matching the location.

So I'll do as I mentioned above: merge, clean up, convert into a "reusing code" section and continue with the plan in the wiki.

hamoid avatar Jun 15 '23 15:06 hamoid