From 04f77b6beabe9d60f21bc3e1a76e9490df5417c9 Mon Sep 17 00:00:00 2001 From: Paulo Feodrippe Date: Wed, 29 Jan 2025 15:06:51 -0500 Subject: [PATCH] [vc] Start to move `update-physics` --- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 1 + src/vybe/c.clj | 168 +++++++++++++++++++++------------------ src/vybe/flecs.clj | 67 +++++++++------- src/vybe/game/system.clj | 68 ++++++++++++++++ src/vybe/panama.clj | 45 ++++++----- todo.md | 2 + 7 files changed, 227 insertions(+), 126 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de893ea8..07ac5ece 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ on: push: branches: - main - - develop + # - develop paths-ignore: - '**/README.md' - '**/CHANGELOG.md' diff --git a/CHANGELOG.md b/CHANGELOG.md index ca6a644f..4ca18e40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Create `vc/eval*` to debug standalone forms - Accept meta `^:void` in `if`/`cond` expressions to signal to the C compiler that we don't care about the return - Move animation-node-player to VybeC + - CPU went from 84% to 63% - Add `vf/defsystem-c` ## v0.7.469 diff --git a/src/vybe/c.clj b/src/vybe/c.clj index 3368dc83..492085ec 100644 --- a/src/vybe/c.clj +++ b/src/vybe/c.clj @@ -1466,7 +1466,7 @@ long long int: \"long long int\", unsigned long long int: \"unsigned long long i ([code-form {:keys [sym-meta sym sym-name] :as opts}] (let [{:keys [c-code ::c-data form-hash final-form init-struct-val]} (-> code-form - (transpile (assoc opts ::version 53))) + (transpile (assoc opts ::version 54))) obj-name (str "vybe_" sym-name "_" (when (or (:no-cache sym-meta) @@ -1733,6 +1733,17 @@ long long int: \"long long int\", unsigned long long int: \"unsigned long long i (defonce -*vybec-fn-watchers (atom {})) +(defn- adapt-form + [form] + (walk/postwalk (fn walk-fn [v] + (if-let [resolved (and (symbol? v) + (some-> (resolve v) deref))] + (if (= (type resolved) ::vp/Opaque) + (symbol (vp/comp-name (vp/component resolved))) + v) + v)) + form)) + (defmacro defn* "Transpiles Clojure code into a C function. @@ -1743,82 +1754,85 @@ long long int: \"long long int\", unsigned long long int: \"unsigned long long i (simple v))" {:clj-kondo/lint-as 'schema.core/defn} [n _ ret-schema args & fn-tail] - `(let [args-desc-1# (quote ~(->> args - (partition-all 3 3) - (mapv (fn [[sym _ schema]] - [sym schema])))) - args-desc-2# ~(->> args - (partition-all 3 3) - (mapv (fn [[sym _ schema]] - [`(quote ~sym) schema])))] - (def ~n - (let [v# (-> (c-compile - {:sym (quote ~n) - :sym-name ~(->name (symbol (str *ns*) (str n))) - :sym-meta ~(meta n) - :ret-schema ~ret-schema - :args (quote ~args)} - - (defn ~n - ~(with-meta (adapt-fn-args args) - (adapt-schema ret-schema)) - ~@fn-tail)) - - (with-meta {::c-function ~(->name (symbol (str *ns*) (str n)))}) - (merge {:fn-desc (into [:fn ~ret-schema] args-desc-2#)})) - - v# (-> (vp/map->VybeCFn (merge v# (-c-fn-builder v#))) - ;; TODO We could put this in the returned map instead of in the metadata. - (with-meta {::c-function ~(->name (symbol (str *ns*) (str n)))})) - - initializer# (when (:init-struct-val v#) - #(when-let [init-struct-val# %] - ((-> ((:symbol-finder v#) - (quote ~(->name (symbol (str *ns*) (str n "__init"))))) - (vp/c-fn [:fn :void [:some_struct [:* :void]]])) - (vp/mem init-struct-val#))))] - - ;; Initialize globals, if any. - (when initializer# - (initializer# (:init-struct-val v#))) - - ;; Return. - (merge v# {:initializer initializer#}))) - - ;; Set some metadata for documentation. - (alter-meta! (var ~n) merge - {:arglists (list args-desc-1#) - :doc (cond-> (format "Returns %s" (if (vp/component? ~ret-schema) - (vp/comp-name ~ret-schema) - (quote ~ret-schema))) - (:doc (meta (var ~n))) - (str "\n\n" (:doc (meta (var ~n)))))}) - - ;; Remove any existing watchers. - (->> (get @-*vybec-fn-watchers (var ~n)) - (mapv (fn [[v# identifier#]] - (remove-watch v# identifier#)))) - ;; Watch global fn pointers vars (if any) so we can have hot reloading. - (let [global-fn-pointers# (:global-fn-pointers (::c-data ~n)) - watchers# (->> global-fn-pointers# - (mapv (fn [{v# :var}] - (let [identifier# (symbol (str "_vybe_c_watcher_" (symbol (var ~n)) "_" (symbol v#)))] - (add-watch v# identifier# - (fn [& _args#] - (try - #_(tap> {:vybec-debug-msg "Updating VybeC fn" - :vybec-fn (var ~n) - :trigger v#}) - (set-globals! ~n {(symbol v#) @v#}) - ;; Trigger var mutation so other vars can know - ;; about it. - (alter-var-root (var ~n) (constantly @(var ~n))) - (catch Exception ex# - (println ex#))))) - [v# identifier#]))))] - (swap! -*vybec-fn-watchers assoc (var ~n) watchers#)) - - (var ~n))) + (let [ret-schema (adapt-form ret-schema) + args (adapt-form args) + fn-tail (adapt-form fn-tail)] + `(let [args-desc-1# (quote ~(->> args + (partition-all 3 3) + (mapv (fn [[sym _ schema]] + [sym schema])))) + args-desc-2# ~(->> args + (partition-all 3 3) + (mapv (fn [[sym _ schema]] + [`(quote ~sym) schema])))] + (def ~n + (let [v# (-> (c-compile + {:sym (quote ~n) + :sym-name ~(->name (symbol (str *ns*) (str n))) + :sym-meta ~(meta n) + :ret-schema ~ret-schema + :args (quote ~args)} + + (defn ~n + ~(with-meta (adapt-fn-args args) + (adapt-schema ret-schema)) + ~@fn-tail)) + + (with-meta {::c-function ~(->name (symbol (str *ns*) (str n)))}) + (merge {:fn-desc (into [:fn ~ret-schema] args-desc-2#)})) + + v# (-> (vp/map->VybeCFn (merge v# (-c-fn-builder v#))) + ;; TODO We could put this in the returned map instead of in the metadata. + (with-meta {::c-function ~(->name (symbol (str *ns*) (str n)))})) + + initializer# (when (:init-struct-val v#) + #(when-let [init-struct-val# %] + ((-> ((:symbol-finder v#) + (quote ~(->name (symbol (str *ns*) (str n "__init"))))) + (vp/c-fn [:fn :void [:some_struct [:* :void]]])) + (vp/mem init-struct-val#))))] + + ;; Initialize globals, if any. + (when initializer# + (initializer# (:init-struct-val v#))) + + ;; Return. + (merge v# {:initializer initializer#}))) + + ;; Set some metadata for documentation. + (alter-meta! (var ~n) merge + {:arglists (list args-desc-1#) + :doc (cond-> (format "Returns %s" (if (vp/component? ~ret-schema) + (vp/comp-name ~ret-schema) + (quote ~ret-schema))) + (:doc (meta (var ~n))) + (str "\n\n" (:doc (meta (var ~n)))))}) + + ;; Remove any existing watchers. + (->> (get @-*vybec-fn-watchers (var ~n)) + (mapv (fn [[v# identifier#]] + (remove-watch v# identifier#)))) + ;; Watch global fn pointers vars (if any) so we can have hot reloading. + (let [global-fn-pointers# (:global-fn-pointers (::c-data ~n)) + watchers# (->> global-fn-pointers# + (mapv (fn [{v# :var}] + (let [identifier# (symbol (str "_vybe_c_watcher_" (symbol (var ~n)) "_" (symbol v#)))] + (add-watch v# identifier# + (fn [& _args#] + (try + #_(tap> {:vybec-debug-msg "Updating VybeC fn" + :vybec-fn (var ~n) + :trigger v#}) + (set-globals! ~n {(symbol v#) @v#}) + ;; Trigger var mutation so other vars can know + ;; about it. + (alter-var-root (var ~n) (constantly @(var ~n))) + (catch Exception ex# + (println ex#))))) + [v# identifier#]))))] + (swap! -*vybec-fn-watchers assoc (var ~n) watchers#)) + + (var ~n)))) #_ (vybe.c/defn* ^:debug eita :- :int [a :- :int] (tap> (+ a 4550))) diff --git a/src/vybe/flecs.clj b/src/vybe/flecs.clj index 48bc136b..68cc65f0 100644 --- a/src/vybe/flecs.clj +++ b/src/vybe/flecs.clj @@ -2195,7 +2195,7 @@ bindings-only-valid (->> bindings (remove (comp keyword? first)) vec) - i 'vybe_c_i + i 'vybe_c_idx it 'vybe_c_it f (fn [idx [k v]] @@ -2234,7 +2234,11 @@ :else v) - flags @*flags] + flags @*flags + meta-default {:idx idx + :sym k + :flags flags + :rvalue v}] ;; We return a vector of vectors because of the ;; destructuring (note that we use `(apply concat)` below. (cond @@ -2243,13 +2247,21 @@ [(with-meta [(symbol (str k "--arr")) ;; TODO support `:maybe` `(vf.c/ecs-field-src ~it ~idx)] - {:idx idx - :type nil - :sym k - :flags flags})] + meta-default)] - (and (vector? v) - #_(some #{:* :_} v)) + ;; Get root entity. + (= v :vf/entity) + [(with-meta [(symbol (str k "--arr")) + `(vp/as (:entities @~it) [:* :long])] + meta-default )] + + ;; Get iter pointer. + (= v :vf/iter) + [(with-meta [(symbol (str k "--arr")) + it] + meta-default)] + + (vector? v) (if (vector? k) (->> k ;; Index for the vector destructuring. @@ -2269,22 +2281,20 @@ (remove nil?) vec) [(with-meta [(symbol (str k "--arr")) - ;; TODO support `:maybe` - `(vf.c/ecs-field-id ~it ~idx)] - {:idx idx - :type nil - :sym k - :flags flags})]) + (if (contains? flags :maybe) + `(when (vf.c/ecs-field-is-set ~it ~idx) + (vf.c/ecs-field-id ~it ~idx)) + `(vf.c/ecs-field-id ~it ~idx))] + meta-default)]) ;; Tag branch. (keyword? v) [(with-meta [(symbol (str k "--arr")) - ;; TODO support `:maybe` - `(vf.c/ecs-field-id ~it ~idx)] - {:idx idx - :type nil - :sym k - :flags flags})] + (if (contains? flags :maybe) + `(when (vf.c/ecs-field-is-set ~it ~idx) + (vf.c/ecs-field-id ~it ~idx)) + `(vf.c/ecs-field-id ~it ~idx))] + meta-default)] ;; Component branch. :else @@ -2297,11 +2307,10 @@ (-field ~it ~v ~idx) (vp/as vp/null [:* ~v])) `(-field ~it ~v ~idx))] - {:idx idx - :type v - :sym k-sym - :binding-form k - :flags flags})]))))) + (merge meta-default + {:type v + :sym k-sym + :binding-form k}))]))))) bindings-processed (->> bindings-only-valid (map-indexed f) @@ -2326,7 +2335,7 @@ (doseq [~i (range (:count @~it))] (let ~ (->> bindings-processed (mapv (fn [k-and-v] - (let [{:keys [sym flags binding-form type idx]} (meta k-and-v) + (let [{:keys [sym flags binding-form type idx rvalue]} (meta k-and-v) [k _v] k-and-v ;; If we are up, it means we are self. ;; TODO Use `is-self` so we can cover up @@ -2362,7 +2371,11 @@ [sym form])) ;; Not a component branch. ;; TODO support `:maybe` - [sym k])))) + [sym + (case rvalue + :vf/entity + `(nth ~k ~i) + k)])))) (apply concat) vec) ~@body)))) diff --git a/src/vybe/game/system.clj b/src/vybe/game/system.clj index e3f9b7ab..69db64fb 100644 --- a/src/vybe/game/system.clj +++ b/src/vybe/game/system.clj @@ -156,6 +156,74 @@ (when-not raycast [:vg/raycast :vg/enabled])]}))) +(vf/defsystem-c ^:debug update-physics-2 w + [ ;; TODO Derive it from transform-global. + scale vt/Scale + {aabb-min :min aabb-max :max} vt/Aabb + vy-body [:maybe vj/VyBody] + transform-global [vt/Transform :global] + kinematic [:maybe :vg/kinematic] + dynamic [:maybe :vg/dynamic] + sensor [:maybe :vg/sensor] + ;; Used to find if we are setting `[:vg/raycast :vg/disabled]` + ;; in Blender. + raycast [:maybe {:flags #{:up :self}} + [:vg/raycast :*]] + phys [:src (root) vj/PhysicsSystem] + ;; TODO `e` should be a `VybeFlecsEntitySet` instead of a `long`. + e :vf/entity + it :vf/iter] + #_(let [half #(max (/ (- (% aabb-max) + (% aabb-min)) + 2.0) + 0.1) + center #(+ (* (/ (+ (% aabb-max) + (% aabb-min)) + 2.0))) + scaled #(* (half %) 2 (scale %)) + {:keys [x y z]} (vm/matrix->translation + (-> (vr.c/matrix-translate (center :x) (center :y) (center :z)) + (vr.c/matrix-multiply transform-global))) + body (if vy-body + (do (when kinematic + #_(println :KINEMATIC (matrix->rotation transform-global)) + (vj/move vy-body (vt/Vector3 [x y z]) (vm/matrix->rotation transform-global) (:delta_time it))) + vy-body) + (let [body (vj/body-add phys (vj/BodyCreationSettings + (cond-> {:position #_(vt/Vector4 [0 0 0 1]) + (vt/Vector4 [x y z 1]) + :rotation #_(vt/Rotation [0 0 0 1]) + (vm/matrix->rotation transform-global) + :shape (vj/box (vj/HalfExtent [(half :x) (half :y) (half :z)]) + scale + #_(vt/Vector4 [x y z 1]) + #_(vt/Translation [0 0 0]) + #_(matrix->rotation transform-global))} + kinematic + (assoc :motion_type (jolt/JPC_MOTION_TYPE_KINEMATIC)) + + sensor + (assoc :is_sensor true) + + dynamic + (assoc :motion_type (jolt/JPC_MOTION_TYPE_DYNAMIC) + :object_layer :vj.layer/moving))))] + (when (= (vf/get-name e) (vf/path [:my/model :vg.gltf/my-cube])) + #_(clojure.pprint/pprint (-> (vj/-body-get phys (:id body)) + :motion_properties + #_(vp/p->map vj/MotionProperties)))) + body)) + {:keys [mesh material]} (when-not vy-body + (gen-cube {:x (scaled :x) :y (scaled :y) :z (scaled :z)} + (rand-int 10)))] + (merge w {(body-path body) + [:vg/debug mesh material phys body + (vt/Eid e)] + + e [phys body + (when-not raycast + [:vg/raycast :vg/enabled])]}))) + (vf/defobserver body-removed w [:vf/events #{:remove} body vj/VyBody diff --git a/src/vybe/panama.clj b/src/vybe/panama.clj index fb890d49..ea8fff1d 100644 --- a/src/vybe/panama.clj +++ b/src/vybe/panama.clj @@ -456,6 +456,11 @@ (to-with-pmap p-map) p-map)) +(defn component? + "Check if value is a IVybeComponent" + [v] + (instance? IVybeComponent v)) + (defn component "Get component, if applicable, otherwise returns `nil`." [maybe-has-component] @@ -463,11 +468,6 @@ (let [^IVybeWithComponent p maybe-has-component] (.component p)))) -(defn component? - "Check if value is a IVybeComponent" - [v] - (instance? IVybeComponent v)) - (defn comp-fields "Get fields of a VybeComponent. @@ -1528,8 +1528,7 @@ schema (last args) opts (cond-> (or opts {}) doc (assoc :doc doc))] - `(do (def ~(with-meta sym (merge {:tag `VybeComponent} - opts)) + `(do (def ~(vary-meta sym merge {:tag `VybeComponent} opts) (make-component (quote ~(symbol (str *ns*) (str sym))) ~opts @@ -1612,20 +1611,24 @@ `(do ~@(->> syms (mapv (fn [sym] - `(let [identifier# (quote ~(symbol (str *ns*) (str `~sym))) - c# (make-component identifier# - {:to-with-pmap (-opaque-to-with-pmap identifier#)} - [[:opaque :pointer]])] - (def ~sym - (reify clojure.lang.IFn - (invoke [_# mem-segment#] - (VybePOpaque. mem-segment# identifier# c#)) - - IVybeWithComponent - (component [_#] - c#))) - #_(cache-comp identifier# ~sym) - ~sym)))))) + (let [comp-sym (symbol (str (str sym) "___comp"))] + `(let [identifier# (quote ~(symbol (str *ns*) (str `~sym)))] + (defcomp ~(with-meta comp-sym + {:private true}) + {:to-with-pmap (-opaque-to-with-pmap identifier#)} + [[:opaque :pointer]]) + + (def ~sym + ^{:type ::Opaque} + (reify clojure.lang.IFn + (invoke [_# mem-segment#] + (VybePOpaque. mem-segment# identifier# ~comp-sym)) + + IVybeWithComponent + (component [_#] + ~comp-sym))) + #_(cache-comp identifier# ~sym) + ~sym))))))) (defn fn-descriptor->map "Ednify fn descriptor. You should use this function if you want to parse diff --git a/todo.md b/todo.md index f43cced3..212a61b2 100644 --- a/todo.md +++ b/todo.md @@ -462,6 +462,8 @@ - [ ] how to represent `nil` properly? - [ ] maybe with nullable - [ ] should have slices? (support for `count`, `first`, `last`, `nth`, iteration) + - [ ] map/filter (reduce behind the scenes) + - [ ] the `->>` macro could optimize and join map and filters - [ ] make it easy to DSP in the frequency domain - [ ] create filters in realtime from clerk - [ ] wasm