i wrote a crude pong in L5. maybe i still need a better title for this library of Processing API in Lua with Love2d. (any suggestions?)
(music by Hildegard Von Bingen of course)
ok, i'm stopping for the night. the next things to do are to add typography functions, full mouse functions, and color modes, then I think that will be most of the API complete. I will still need to do bug testing.
Tonight I ran up against a problem I couldn't quite solve. I am using @Alexjgriffith suggestion to modify love2d's love.run() game loop and modified the default loop to not clear the screen between draw calls. Unfortunately, when drawing graphics from events (such as keyPressed(), keyTyped() ) the screen flickers, I think because of double drawing. I tried half a dozen different solutions but never quite found a satisfactory one unfortunately.
added frameRate() function.
while working on all this, had a bit of a think. i believe my flickering screen problem is a result of me hacking Love2d to work more like processing/p5. in the processing model, the approach is a synchronous model. draw runs once per frame. if too much processing happens, it slows down/frame rate drops. love2d takes the approach of drawing as many times as needed per game loop. logic is separated out into update(), while drawing is rendered in a separate thread, so game logic doesn't block rendering and realtime logic can be processed. In love2d, the emphasis is on realtime game processes.:
Don't want to jinx myself but I think I'm nearing completion of what I've set for my first run implementation of Processing API in Lua (via Love2d). I need to add in typography styling, some input/output options, and then probably have some drawing buffer debugging to do (which has been the most difficult thing for me to work through so far).
Today I added in describe() mode from p5.js and decided to try having it output text to the console for CLI screenreaders, but I need to look into more best practices for this.
I also implemented color tables, getting and setting colors, screenshotting (save() ) , smoothing/noSmoothing, strokeCaps, strokeJoins, displayWidth, displayHeight, millis(), mouseWheel() function.
i had temporarily for a day or two jinxed myself but i was able to figure out font loading and textSize, and other typography functions to emulate the processing api / flow (maintaining state).
next i turn to input/output (importing and exporting strings/json/tables?).
completed! loadStrings(), saveStrings(), loadTable(), and saveTable() complete. And current data formats are csv, tsv and lua.
I considered adding in a loadJSON() function, and still could at some point, but didn't think it made sense to DIY build a basic JSON parser or drop in something now. Could always be added later.
And with that, i've pretty much finished most of this first draft of implementing the API. there's a few remaining color modes to add in: HSB, a noise function, some vertex functions, and then lots of debugging and refactoring. notably, i have not tested on a Mac or Windows machine yet, or any computer other than my own! I also need to review how i'm implementing drawing from setup(). my current method is a bit dumb and draws twice. but ultimately, feeling pretty good about the library so far.
fiinished up a heckuva of a lot of the API and have been doing some tests (still need to do some more). I still need to add in colormodes HSB, HSA and a few related functions and then that'll be the first draft of L5. i am ignoring vertex functions for now (I could'nt quite wrap my head around it yet) and need to fix arcs.
I started building out intro documentation yesterday. I saw Casey and Lauren today and told them about the project and will share it with them hopefully soon. Lauren asked me about what if it catches on and I'm working on this for years and years lol.
Now I'm starting to think through documentation and reference sites. I looked at how they build the sites for Processing and p5 and I don't quite want to have to learn JSX and MDX. It's probably not too bad but I'm hoping for something much simpler at first. I can always build it out further later.
Anyone have a suggestion for building a programming language documentation and reference site? particularly that's beginner-friendly, emphasizes the reference, and makes it easy to handle/build a reference page for all of the language's functions? written in markdown, produces static pages, and can be hosted on a git forge and collaboratively built with others.
I am currently looking at:
* hugo documentation themes
* MKDocs
* adapting my own static site generator again - though this may not be robust enough over time
* i keep reading about documentation generators but i don't really grok the idea yet
there's not too much to this sketch, but it's fun trying out rapid code sketching with the language now
code:
require("L5")
function setup()
size(1920,1280)
windowTitle("Flag Thing")
noStroke()
createPage()
frameRate(1)
end
function draw()
createPage()
end
function createPage()
background(random(255),random(255),random(255))
fill(random(255),random(255),random(255))
circle(width/2,height/2,2/3*height)
end
with a bit of a whimper at the end of a friday night here i have implemented 99% of the API to an unpolished, un-refactored state. I've also assembled a bit of a TODO list so I can go in and start cleaning things up.
Today I added in colorMode() which lets you change between RGB, HSB and HSL. I thought I had it, but since I'm trying to emulate Processing and p5.js I always test against their outcomes and was dismayed to learn that using a html color name in p5.js even in another color mode should always output in RGB. And while this makes complete sense, I kept getting strange color output when rendering "orange" in HSL mode for example. I'm guessing my color juggling is off somewhere because it goes like this:
code:
colormode(HSL)
fill("orange")
square(0,0,100)
behind the scenes:
1. change global color mode state to HSL and max vals for HSLA are 360,100,100,100 default
2. retrieve orange as RGB(A) values. convert that to "orange" in HSL.
3. draw the square in that fill color
..
4. Hmm, yellow appears instead?
The problem exists between 2 and 3 (as well as between my ears).
just wasting time.
coded a basic chess set with manual placement of pieces, so not a really playable game yet, though maybe hexapawn soon though? (no promises!)
@exquisitecorp afaik, unless i’ve misunderstood your post - love2d does not run update and draw on separate threads. in its vanilla configuration, update is run first, draw second. they're both run in the same (blocking) single thread.
i can confirm that via experience - framerate absolutely can tank due to too many update calls. i had some nasty bad code that was checking 100,000,000 coordinates per frame and brought fps to its knees 😅
@vga256 ah ok, thank you for clarifying that.
oh and i've made good progress! fingers crossed, but everything seems to be rendering across draw and events. now i can't seem to get setup to render correctly, but hopefully only a matter of time. need to do more testing but feeling better.
@exquisitecorp woohoo. glad to hear it!
always a drag when progress gets hung up on a single annoying bug
@exquisitecorp is it possible that you could queue up the draw calls in the love.update() loop instead of love.draw()? i believe this would allow everything to be processed in the update before the draw call is ever made, and might prevent the buffer problem.
i'm thinking of love.update, because i *think* keypressed and other events are processed around that same cycle as well
here's my present love.run() in case it sparks some brainstorming :)
ok headed to sleep where i'll be murmuring about this
-- Custom love.run() function that doesn't clear screen between frames but does clear matrix transformation
function love.run()
if love.load then love.load(love.arg.parseGameArguments(arg), arg) end
-- We don't want to clear the screen automatically
if love.timer then love.timer.step() end
local dt = 0
-- Main loop
return function()
-- Process events
if love.event then
love.event.pump()
for name, a,b,c,d,e,f in love.event.poll() do
if name == "quit" then
if not love.quit or not love.quit() then
return a or 0
end
end
love.handlers[name](a,b,c,d,e,f)
end
end
-- Update dt
if love.timer then dt = love.timer.step() end
-- Update
if love.update then love.update(dt) end
-- Draw (without clearing!)
if love.graphics and love.graphics.isActive() then
love.graphics.origin()
-- DON'T call love.graphics.clear() here!
if love.draw then love.draw() end
love.graphics.present()
end
if love.timer then love.timer.sleep(0.001) end
end
end
@exquisitecorp so here's what I was wondering: when your Processing functions are called, which loop are they being called from? e.g. update()? draw()?
i can see in the custom love.run() that the event queue is being processed before update() and draw() which might lead to your Processing commands being queued or drawn out-of-order. e.g. hitting a keypress (which love2d treats as an Event) queues a Processing command *before* love2d gets to the ones that exist in update() or draw(). this would create some very unpredictable behaviour. just a hunch.
@vga256 i'm bookmarking this forum thread too where i believe this or similar issue is discussed, though it is marked "solved" but i see no solution there
https://www.love2d.org/forums/viewtopic.php?t=86140
i also tried adding a blocker so that when events happen the main draw doesn't run. that didn't solve it either. i've tried so many things that didn't work that i'm feeling better about the idea that i may find something that does for ruling everything else out! lol
@vga256 ohohoho i've figured out something.
ok, so instead of using my events like keyTyped() i instead temporarily just added a flag inside keyTyped and set it to true. Then in my draw if the flag is set to true I draw the elements that were previously inside keyTyped (and i set the flag to off in keyReleased(). ) That worked! Ok, so i'm closer to finding a solution. i need to take a break but will come back at this tonight. i think i'm onto a solution i hope, though i don't want to jinx myself.
my testing file: https://gist.github.com/lee2sman/8ca583a11c79705fd030b3953a157476
@exquisitecorp ah ha, that really does suggest it that it's because of the order of execution... (keypressed) Events will always be executed first. one option might be something similar to your flag approach, but instead of false/true, having it add each command to a command queue in draw()
update: wrapped each draw function in a closure/anonymous function instead, and clear the queue once per frame:
command_queue = {}
function keyTyped()
if key=='c' then
command_queue[#command_queue + 1] = function() background(0,0,255) end
elseif key == 'p' then
command_queue[#command_queue + 1] = function() background(255,0,255) end
end
end
function draw()
rect(0,0,100,100)
if mouseX>width/2 then
background(255,0,0)
end
-- process the command queue in order
for i, command in ipairs(command_queue) do
command()
end
-- now clear the queue so it doesn't repeat the same commands every frame
command_queue = {}
end
@exquisitecorp ack the error was mine. table.insert doesn't like inserting functions as a parameter!
function keyTyped()
if key=='c' then
command_queue[#command_queue + 1] = background(0,0,255)
elseif key == 'p' then
command_queue[#command_queue + 1] = background(255,0,255)
end
end
edit: the more i think about this, the less i think it'll work. i suspect lua executes background(0,0,255) the instant it is used as an argument in the keyTyped() function, instead of storing it as a value... it stores the executed value as a value.
i think it needs to be stored as a closure/anonymous function instead:
function keyTyped()
if key=='c' then
command_queue[#command_queue + 1] = function() background(0,0,255) end
elseif key == 'p' then
command_queue[#command_queue + 1] = function() background(255,0,255) end
end
end
@vga256 partial-solution! very dumb approach but kinda sorta working:
When drawing a background (which is essentially clearing the screen and drawing a color), i just store the color to a global table holding that background color. I also set a flag to draw the background color. Inside the love.draw() i check for that flag, then clear the screen with whatever the last saved color was. Then i run the rest of the draw() function.
why this isn't a perfect solution: it assumes drawing a background color will always be first before drawing everything else, which isn't a perfect assumption. need to think some more.
@exquisitecorp interesting and yeah - a bit janky haha.
what did you think of the forum post suggesting painting everything to a Canvas instead of the love frame buffer?