Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Garak (hot reloading / REPL) #17

Draft
wants to merge 23 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
677151d
Update mruby code. Update .a builds for Linux
awfulcooking Apr 28, 2023
9fb39af
Add --allow-multiple-definition ldflag
awfulcooking Apr 28, 2023
1b546a9
Update src/main.cpp for mruby API changes.
awfulcooking Apr 28, 2023
f445b7a
Set $0 global variable (script name)
awfulcooking Apr 28, 2023
fcc59f8
Make alpha parameter to Color.new() optional
awfulcooking Apr 28, 2023
ffe7e33
Add default_font method to Kernel (text.cpp)
awfulcooking Apr 28, 2023
cb35f70
Add constant Color = Colour (alias)
awfulcooking Apr 28, 2023
f39f707
Add Colour[] and Rectangle[] methods for making simple colors and shapes
awfulcooking Apr 28, 2023
dc21d1f
Add setup_Rectangle() so Rectangle's can be initialized outside of re…
awfulcooking Apr 28, 2023
62f993c
Add get_collision_rec(r1, r2) and check_collision_recs(r1, r2) to Ruby
awfulcooking Apr 28, 2023
c7bc02b
Add game prototype with hot-reloading 🕹️
awfulcooking Apr 28, 2023
cfad6b9
Add copy of linux.rb (mruby build config)
awfulcooking Apr 28, 2023
e319c3f
Rakefile: drop --allow-multiple-definition except on Linux
awfulcooking May 8, 2023
41dd78e
mruby: switch to iij/mruby-require
awfulcooking May 8, 2023
05ac0f7
Add mruby-file-stsat and mruby-ostruct gems
awfulcooking May 8, 2023
9de7972
Comment out mruby-uri-parser dep for now on all platforms
awfulcooking May 8, 2023
3b59250
Remove --allow-multiple-definition workaround for Linux
awfulcooking May 8, 2023
56b2de5
android.rb: Fix cross-build of mrbgems with autotools scripts
awfulcooking May 8, 2023
1752b7c
Rebuild mruby for all platforms
awfulcooking May 8, 2023
2a444bc
Merge branch 'main' into Garak
awfulcooking May 8, 2023
9f64111
Delete linux-host-config-for-mruby.rb
awfulcooking May 8, 2023
9b276b7
Implement Vector3
awfulcooking Sep 28, 2023
39b94af
Implement Camera3D, begin_mode3D, end_mode3D
awfulcooking Sep 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions game/game.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
require "jump"
return

def jump_sound
@jump_sound ||= load_sound("jump.wav")
end

def init
set_window_size 800, 600
end

include Garak

def tick
if key_pressed?(KEY_SPACE)
jump_sound.play
end

drawing do
clear_background Colour[
Math.sin(Garak.frame_count / 77.0).+(1)./(2).*(120).round,
Math.sin(Garak.frame_count.+(30) / 66.0).+(1)./(2).*(120).round,
Math.sin(Garak.frame_count.-(70) / 55.0).+(1)./(2).*(120).round,
]

if mouse_button_down?(MOUSE_LEFT_BUTTON) or mouse_button_pressed?(0)
draw_rectangle 0, 0, get_screen_width, get_screen_height, Colour.new(0, 0, 0, 50)
end

draw_text "frame #{Garak.frame_count}", 20, get_screen_height - 40, 20, ORANGE

# draw_center_text "Hello world #{Garak.frame_count}", 32, YELLOW, default_font, 10
draw_center_text "#{get_mouse_position.x}, #{get_mouse_position.y}", 32, YELLOW, default_font, 10
end
end

def draw_center_text(text, size, color, font=default_font, padding=0, hack=false)
measured = measure_text_ex(font, hack ? "W" * text.size : text, size, padding)
# position = Vector2.new(get_screen_width/2 - measured.x/2, get_screen_height/2 - measured.y/2)
position = Vector2[get_screen_width/2 - measured.x/2, get_screen_height/2 - measured.y/2]
draw_text_ex(font, text, position, size, padding, color)
end
174 changes: 174 additions & 0 deletions game/garak.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
TOP_LEVEL = self

module ::Garak
extend self

require "garak/ansi"
require "garak/repl"

GAME_FILE = "game.rb"

def Garak.boot()
return false if @@booted ||= false
@@booted = true

init_window(1280, 720, "Game")

set_window_state(FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE)
set_window_min_size(100, 100)

set_target_fps get_monitor_refresh_rate get_current_monitor

init_audio_device

require GAME_FILE
end

begin :logs
def log(*msgs) = puts(*msgs)
end

def ohai() = "hey there! :-)"

begin :reload
def game_file_changed?(path=GAME_FILE)
@game_file_stat ||= {}

old_stat = @game_file_stat[path]
new_stat = @game_file_stat[path] = File.stat(path)

old_stat and old_stat.mtime != new_stat.mtime
end

def reloadable_files
[*$", __FILE__].uniq
end

def reload_changed_files!
for file in reloadable_files
reload!(file) if game_file_changed?(file)
end
end

def reload!(path=GAME_FILE)
log ANSI.yellow "=> Reload #{path}"
clear_tick_error
TOP_LEVEL.load path
end
end

begin :frame_count
@@frame_count ||= 0

def frame_count() = @@frame_count
def first_frame?() = (@@frame_count == 1)
def advance_frame_count() = @@frame_count += 1
end

begin :repl
@@repl ||= REPL.new(prompt: "Garak|> ")

def repl
@@repl
end

def repl=(repl)
@@repl = repl
end
end

def play
return set_main_loop 'tick!' if browser?

@@repl&.prompt!
until window_should_close?
reload_changed_files!
@@repl&.tick!

if get_tick_error
draw_tick_error!
else
tick!
end
end

shutdown
end

def tick!
advance_frame_count
init if first_frame?
TOP_LEVEL.tick
rescue SystemExit
raise
rescue Exception => ex
backtrace = ex.backtrace[..-4].map do |line|
line.delete_prefix(Dir.pwd).delete_prefix("/")
end

msg = <<~msg
#{ex.message}
#{backtrace.map! { " #{_1}" }.join "\n"}
msg

log ANSI.red msg
set_tick_error msg
draw_tick_error! msg
end

def draw_tick_error!(msg=get_tick_error)
drawing {
clear_background(BLACK)
draw_text_ex(default_font, msg, Vector2.new(20, 20), 20, 2, RED)
}
end

def get_tick_error() = @__tick_error__
def set_tick_error(msg) = @__tick_error__ = msg
def clear_tick_error() = set_tick_error(nil)

def shutdown(exit=true)
log ANSI.yellow "Shutdown"

close_audio_device
close_window
Kernel.exit if exit
end
end

module ::Garak
module Numeric
def hertz() = 1.0 / self
alias :hz :hertz
end

::Numeric.include Numeric
end

module ::Garak
module Throttle
@@times = {}

def throttle(interval=1.0, task=caller[0])
last = @@times[task]
now = get_time

if !last || last <= (now - interval)
@@times[task] = now
yield
end
end
end

::Object.include Throttle
end

# Showtime.

begin
::Garak.boot
::Garak.play
rescue SystemExit
return
end

48 changes: 48 additions & 0 deletions game/garak/ansi.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
module ::Garak
module ANSI
extend self

COLORS = {
black: 30, red: 31, green: 32, yellow: 33,
blue: 34, magenta: 35, cyan: 36, white: 37,

bright_black: "1;30", bright_red: "1;31", bright_green: "1;32",
bright_yellow: "1;33", bright_blue: "1;34", bright_magenta: "1;35",
bright_cyan: "1;36", bright_white: "1;37",
}

STYLES = {
reset: 0, bold: 1, italic: 3, underline: 4,
blink: 5, reverse: 6,
}

def color(name, text=self, suffix=reset)
return text unless name
"\e[#{COLORS[name] or raise ArgumentError.new("unknown color: #{name.inspect}")}m#{text}#{suffix}"
end

def style(name, text=self, suffix=reset)
return text unless name
"\e[#{STYLES[name] or raise ArgumentError.new("unknown style: #{name.inspect}")}m#{text}#{suffix}"
end

def method_missing(name, *)
key = name.to_s.delete_prefix("ansi_").to_sym
if COLORS.key?(key)
send define_method(name) { |text=self,suffix=reset| color(key, text, suffix) }
elsif STYLES.key?(key)
send define_method(name) { |text=self,suffix=reset| style(key, text, suffix) }
else
super
end
end

def reset(text="") = style(:reset, text, nil)
def ansi_reset(text="") = style(:reset, text, nil)

for name in [*COLORS.keys, *STYLES.keys]
send(name)
send("ansi_#{name}")
end
end
end
97 changes: 97 additions & 0 deletions game/garak/repl.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
require "garak/ansi"

TOP_LEVEL ||= self

module ::Garak
class REPL
attr_accessor :stdin, :stdout, :prompt, :buffer
attr_accessor :prompt_color, :error_color, :result_color
attr_accessor :last_result

def initialize(stdin=$stdin, stdout=$stdout,
prompt: "repl> ",
prompt_color: :bright_green,
error_color: :bright_magenta,
result_color: :bright_yellow
)
@buffer = ""
@stdin = stdin
@stdout = stdout
@prompt = prompt
@prompt_color = prompt_color
@error_color = error_color
@result_color = result_color
end

def tick!
did_read = false
while IO.select([@stdin], nil, nil, 0)
@buffer += @stdin.getc.to_s
did_read = true
end

if !did_read and [email protected]?
begin
eval! @buffer
ensure
@buffer.clear
prompt!
end
end
end

def run_blocking!(...)
prompt!
while line = @stdin.readline rescue nil
eval!(line, ...)
prompt!
end
end

def puts(*msgs)
for line in msgs.map! { |msg| msg.to_s.lines }.flatten
print "\e7"
print "\r\n"
print "\e[1F"
print "\e[1L"
print "\e6"
super(line.chomp!)
print "\e8"
print "\e[1B"
print "\e9"
end
end

def sprint(*args)
puts args.inspect
end

def _() = @last_result

def eval!(code)
return if code.strip.empty?

_ = @last_result
@last_result = TOP_LEVEL.eval(code)
print_result!
rescue Exception => ex
print_error! ex
end

def prompt!(prompt=@prompt, color=@prompt_color, input_color=@input_color)
print ANSI.color(color, prompt), ANSI.reset
end

def print_error!(ex, color=@error_color)
print ANSI.color(color, <<~error)
#{ex.message.strip}
#{ex.backtrace.join("\n ")}
error
print ANSI.reset
end

def print_result!(object=@last_result, color=@result_color)
print ANSI.color(color, " => #{object.inspect}\n"), ANSI.reset
end
end
end
Loading