|
(ns vybe.raylib.impl |
|
(:require |
|
[clojure.string :as str] |
|
[camel-snake-kebab.core :as csk]) |
|
(:import |
|
(org.raylib raylib_h Color) |
|
(java.lang.foreign Arena MemorySegment MemoryLayout ValueLayout) |
|
(jdk.internal.foreign.layout ValueLayouts))) |
|
|
|
(defonce *state |
|
(atom {:buf-general [] |
|
:buf1 [] |
|
:buf2 [] |
|
:front-buf? true})) |
|
|
|
(defn add-command |
|
([cmd] |
|
(add-command cmd {})) |
|
([cmd {:keys [general]}] |
|
(locking *state |
|
(swap! *state (fn [state] |
|
(if general |
|
(-> state |
|
(update :buf-general conj cmd)) |
|
(-> state |
|
(update :buf1 conj cmd) |
|
(update :buf2 conj cmd)))))))) |
|
|
|
(def ^:private declared-methods |
|
(concat (:declaredMethods (bean org.raylib.raylib_h)) |
|
(:declaredMethods (bean org.raylib.raylib_h_1)))) |
|
|
|
(defn- ->type |
|
[v] |
|
(let [n (.name v) |
|
type-name (when (.isPresent n) |
|
(.get n))] |
|
(cond |
|
(and (= (type v) |
|
jdk.internal.foreign.layout.ValueLayouts$OfAddressImpl) |
|
(not type-name)) |
|
:pointer |
|
|
|
type-name |
|
(keyword "raylib" type-name) |
|
|
|
:else |
|
(case (symbol (.getName (class v))) |
|
jdk.internal.foreign.layout.ValueLayouts$OfDoubleImpl |
|
:double |
|
|
|
jdk.internal.foreign.layout.ValueLayouts$OfLongImpl |
|
:long |
|
|
|
jdk.internal.foreign.layout.ValueLayouts$OfFloatImpl |
|
:float |
|
|
|
jdk.internal.foreign.layout.ValueLayouts$OfIntImpl |
|
:int |
|
|
|
jdk.internal.foreign.layout.ValueLayouts$OfShortImpl |
|
:short |
|
|
|
jdk.internal.foreign.layout.ValueLayouts$OfCharImpl |
|
:char |
|
|
|
jdk.internal.foreign.layout.ValueLayouts$OfByteImpl |
|
:byte |
|
|
|
jdk.internal.foreign.layout.ValueLayouts$OfBooleanImpl |
|
:boolean)))) |
|
|
|
(defn layout? |
|
[t] |
|
(and t |
|
(= (namespace t) |
|
"raylib"))) |
|
|
|
(defn address? |
|
[t] |
|
(or (layout? t) |
|
(= t :pointer))) |
|
|
|
(defn raylib-methods |
|
[] |
|
(->> declared-methods |
|
(filter #(str/includes? (.getName %) "$descriptor")) |
|
#_(filter #(= (.getName %) "GetMonitorName$descriptor")) |
|
#_(take 10) |
|
(mapv (fn [method] |
|
(let [desc (.invoke method nil (into-array Object [])) |
|
args (.argumentLayouts desc) |
|
|
|
ret' (.returnLayout desc) |
|
ret (when (.isPresent ret') |
|
(->type (.get ret'))) |
|
|
|
desc-name ((comp :name bean) method) |
|
main-name (str/replace desc-name #"\$descriptor" "") |
|
main-method (->> declared-methods |
|
(filter (comp #(= main-name |
|
(.getName %)))) |
|
first)] |
|
#_(def -ddd |
|
[[:desc desc] |
|
[:args args] |
|
[:ret ret] |
|
[:desc-name desc-name] |
|
[:main-name main-name] |
|
[:main-method main-method]]) |
|
(when-not main-method |
|
(throw (ex-info "Method for desc does not exist" |
|
{:desc desc |
|
:desc-name desc-name}))) |
|
(let [args (mapv (fn [v param] |
|
{:name (.getName param) |
|
:clj-type (if (= v :panama/allocator) |
|
v |
|
(->type v))}) |
|
args |
|
;; If return is a layout, the method |
|
;; receives an allocator (e.g. Arena) as |
|
;; the first arg. |
|
(if (layout? ret) |
|
(rest (.getParameters main-method)) |
|
(.getParameters main-method)))] |
|
(vector main-name |
|
{:args args |
|
:ret ret |
|
:has-arena? (or (layout? ret) |
|
(some (comp address? :clj-type) |
|
args)) |
|
:main-thread? (nil? ret)}))))))) |
|
#_ (def methods-to-intern (raylib-methods)) |
|
|
|
(defonce default-arena |
|
(Arena/ofAuto)) |
|
|
|
(defmacro t |
|
"Runs command (delayed) in the main thread. |
|
|
|
Useful for REPL testing as it will block and return |
|
the result from the command." |
|
[& body] |
|
`(let [*res# (promise)] |
|
(add-command |
|
(fn [] |
|
(let [v# ~@body] |
|
(deliver *res# v#) |
|
v#)) |
|
{:general true}) |
|
(let [res# (deref *res# 500 ::error)] |
|
(when (= res# ::error) |
|
(throw (ex-info "Error while running command" |
|
{:form (quote ~&form) |
|
:form-meta ~(meta &form)}))) |
|
res#))) |
|
|
|
(defn first-thread? |
|
[] |
|
(= (.getId (Thread/currentThread)) 1)) |
|
|
|
(defn try-string |
|
[s] |
|
(if (string? s) |
|
(.allocateFrom default-arena s) |
|
s)) |
|
|
|
(def any-thread-methods |
|
#{"WindowShouldClose" |
|
"GetMonitorName"}) |
|
|
|
(defmacro -intern-methods |
|
[init size] |
|
`(do ~(->> (raylib-methods) |
|
(drop init) |
|
(take size) |
|
(mapv (fn [[n {:keys [args ret has-arena? main-thread?]}]] |
|
(let [ray-args (mapv (fn [{:keys [name clj-type]}] |
|
(if (= clj-type :pointer) |
|
``(try-string ~~(symbol name)) |
|
(symbol name))) |
|
args)] |
|
(try |
|
`(defmacro ~(csk/->kebab-case-symbol (str n (if main-thread? |
|
"!" |
|
""))) |
|
{:arglists (list |
|
(quote |
|
~(mapv (fn [{:keys [name clj-type]}] |
|
[(symbol name) clj-type]) |
|
args))) |
|
:doc ~(format "Returns %s." (or ret "void"))} |
|
;; Fn args. |
|
~(mapv (comp symbol :name) args) |
|
;; Fn body. |
|
~(cond |
|
;; Functions that start with `Is` and other |
|
;; prefixes can be safely run outside the main |
|
;; thread. |
|
(or (str/starts-with? n "Is") |
|
(contains? any-thread-methods n)) |
|
``(~(symbol "org.raylib.raylib_h" ~n) |
|
~@~(vec |
|
(concat |
|
(when (and has-arena? (layout? ret)) |
|
[``default-arena]) |
|
ray-args))) |
|
|
|
(or (not main-thread?) |
|
(and main-thread? |
|
(str/includes? n "Window"))) |
|
``(if (first-thread?) |
|
(~(symbol "org.raylib.raylib_h" ~n) |
|
~@~(vec |
|
(concat |
|
(when (and has-arena? (layout? ret)) |
|
[``default-arena]) |
|
ray-args))) |
|
(t (~(symbol "org.raylib.raylib_h" ~n) |
|
~@~(vec |
|
(concat |
|
(when (and has-arena? (layout? ret)) |
|
[``default-arena]) |
|
ray-args))))) |
|
|
|
:else |
|
;; Main thread. |
|
``(if (first-thread?) |
|
(~(symbol "org.raylib.raylib_h" ~n) |
|
~@~(vec |
|
(concat |
|
(when (layout? ret) |
|
[''default-arena]) |
|
ray-args))) |
|
(add-command |
|
(with-meta |
|
(fn ~'~'--internal-fn |
|
([] |
|
~~(if has-arena? |
|
``(~'~'--internal-fn default-arena) |
|
``(~'~'--internal-fn nil))) |
|
([~'~'arena] |
|
;; (org.raylib.raylib_h/WHATEVER [allocator?] and some args) |
|
(~(symbol "org.raylib.raylib_h" ~n) |
|
~@~(vec |
|
(concat |
|
(when (layout? ret) |
|
[''arena]) |
|
ray-args))))) |
|
{:form (quote ~~'&form)}))))) |
|
(catch Error _e |
|
nil)))))))) |
|
|
|
(def intern-methods |
|
(memoize |
|
(fn [] |
|
(mapv (fn [n] |
|
;; We use `eval` to avoid macroexpansion of |
|
;; all the methods, which would give us a |
|
;; "method too large" error. |
|
(eval `(-intern-methods ~(* n 100) 100))) |
|
(range (inc (int (/ (count (raylib-methods)) |
|
100)))))))) |
|
#_(intern-methods) |
|
#_(macroexpand-1 '(-intern-methods 300 10)) |
|
#_(meta #'draw-text!) |
|
|
|
#_(macroexpand-1 '(load-model "OOOB")) |
|
#_(macroexpand-1 '(update-camera! 1 2)) |
|
#_(macroexpand-1 '(get-monitor-name 0)) |
|
|
|
(comment |
|
|
|
()) |