Merge branch 'lang/foundations' into lang/nav
This commit is contained in:
commit
c1dc475438
32 changed files with 45128 additions and 44582 deletions
10
changes/render3d-moltenvk.md
Normal file
10
changes/render3d-moltenvk.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
bump: minor
|
||||
type: feature
|
||||
**ludic.render3d carries MoltenVK, so a Mac program has its Vulkan driver without the SDK.**
|
||||
`native/build.sh` builds MoltenVK from a pinned tag (v1.4.2, its SHA-256 checked; its own
|
||||
dependencies at the commits its ExternalRevisions pins), thinned to arm64 and signed, into
|
||||
`lib/macos-arm64/libMoltenVK.dylib`, with its licence in `native/LICENSE-MoltenVK`. Every program
|
||||
that imports render3d links it through its rpath - the package's lib/ while developing,
|
||||
`Contents/Frameworks` in a bundle, where `ludic bundle` puts and signs it - and the loader
|
||||
(`vk_mac.ll`) now also looks for `@rpath/libMoltenVK.dylib`. An SDK loader found first still wins,
|
||||
so the validation layer can be stacked in development. `gpu_is_gl()` is gone.
|
||||
|
|
@ -45,6 +45,9 @@ story_marks()
|
|||
let marks = compass_capture_end()
|
||||
```
|
||||
|
||||
The list and its marks are the compass's own, reused, and good until the next capture begins: read
|
||||
them then, or copy what must last longer.
|
||||
|
||||
## The port
|
||||
|
||||
```ludic
|
||||
|
|
|
|||
26
packages/ludic.compass/capture.ludic
Normal file
26
packages/ludic.compass/capture.ludic
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# capture.ludic - someone else wants to know where the providers point (a guide's arrow): until the
|
||||
# end every mark is theirs, whatever the tier, and the frame's list is left alone
|
||||
export function compass_capture_begin(compass_st: mut CompassState) -> void {
|
||||
if compass_st.cmp_caught == null {
|
||||
compass_st.cmp_caught = new []CompassMark
|
||||
compass_st.cmp_cpool = new []CompassMark
|
||||
}
|
||||
List.clear(compass_st.cmp_caught)
|
||||
compass_st.cmp_cused = 0
|
||||
compass_st.cmp_capturing = true
|
||||
}
|
||||
|
||||
# what was caught: the compass's own list and marks, good until the next capture begins
|
||||
export function compass_capture_end(compass_st: mut CompassState) -> []CompassMark {
|
||||
compass_st.cmp_capturing = false
|
||||
if compass_st.cmp_caught == null { compass_st.cmp_caught = new []CompassMark }
|
||||
return compass_st.cmp_caught
|
||||
}
|
||||
|
||||
# the capture pool's next mark, filled (one made only when the pool is short)
|
||||
function cmp_catch(compass_st: mut CompassState, x: float, z: float, icon: int, colour: int, tier: int, prio: int, label: string) -> CompassMark {
|
||||
if compass_st.cmp_cpool == null { compass_st.cmp_cpool = new []CompassMark }
|
||||
if compass_st.cmp_cused >= len(compass_st.cmp_cpool) { push(compass_st.cmp_cpool, new CompassMark) }
|
||||
compass_st.cmp_cused += 1
|
||||
return cmp_fill(compass_st.cmp_cpool[compass_st.cmp_cused - 1], x, z, icon, colour, tier, prio, label)
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ export function compass_begin(compass_st: mut CompassState) -> void {
|
|||
export function compass_mark(compass_st: mut CompassState, x: float, z: float, icon: int, colour: int, tier: int, prio: int, label: string) -> void {
|
||||
if compass_st.cmp_list == null { compass_begin(compass_st) }
|
||||
if compass_st.cmp_capturing {
|
||||
push(compass_st.cmp_caught, cmp_new(x, z, icon, colour, tier, prio, label)) # kept by the listener
|
||||
push(compass_st.cmp_caught, cmp_catch(compass_st, x, z, icon, colour, tier, prio, label))
|
||||
return
|
||||
}
|
||||
if CompassWorld.tier() < tier { return }
|
||||
|
|
@ -38,16 +38,8 @@ export function compass_end(compass_st: mut CompassState) -> void {
|
|||
# the pool's next mark, filled (one made only when the pool is short)
|
||||
function cmp_take(compass_st: mut CompassState, x: float, z: float, icon: int, colour: int, tier: int, prio: int, label: string) -> CompassMark {
|
||||
if compass_st.cmp_used >= len(compass_st.cmp_pool) { push(compass_st.cmp_pool, new CompassMark) }
|
||||
let m = compass_st.cmp_pool[compass_st.cmp_used]
|
||||
compass_st.cmp_used += 1
|
||||
m.x = x
|
||||
m.z = z
|
||||
m.icon = icon
|
||||
m.colour = colour
|
||||
m.tier = tier
|
||||
m.prio = prio
|
||||
m.label = label
|
||||
return m
|
||||
return cmp_fill(compass_st.cmp_pool[compass_st.cmp_used - 1], x, z, icon, colour, tier, prio, label)
|
||||
}
|
||||
|
||||
function cmp_band(p: int) -> int { return Math.clamp(p, COMPASS_PLAIN, COMPASS_TRACKED) }
|
||||
|
|
@ -63,19 +55,4 @@ export function compass_gather(compass_st: mut CompassState) -> void {
|
|||
compass_end(compass_st)
|
||||
}
|
||||
|
||||
# someone else wants to know where the providers point (a guide's arrow): until the end every
|
||||
# mark is theirs, whatever the tier, and the frame's list is left alone
|
||||
export function compass_capture_begin(compass_st: mut CompassState) -> void {
|
||||
compass_st.cmp_capturing = true
|
||||
compass_st.cmp_caught = new []CompassMark
|
||||
}
|
||||
|
||||
export function compass_capture_end(compass_st: mut CompassState) -> []CompassMark {
|
||||
compass_st.cmp_capturing = false
|
||||
var got = compass_st.cmp_caught
|
||||
compass_st.cmp_caught = new []CompassMark
|
||||
if got == null { got = new []CompassMark }
|
||||
return got
|
||||
}
|
||||
|
||||
export function compass_capturing(compass_st: CompassState) -> bool { return compass_st.cmp_capturing }
|
||||
|
|
|
|||
|
|
@ -7,5 +7,6 @@ import "ludic.base"
|
|||
import "ports.ludic"
|
||||
import "marks.ludic"
|
||||
import "gather.ludic"
|
||||
import "capture.ludic"
|
||||
import "bearing.ludic"
|
||||
import "radar.ludic"
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ export state CompassState {
|
|||
cmp_used: int = 0
|
||||
cmp_capturing: bool = false
|
||||
cmp_caught: []CompassMark = null # what providers said while someone was listening
|
||||
cmp_cpool: []CompassMark = null # the capture's marks, reused from one capture to the next
|
||||
cmp_cused: int = 0
|
||||
cmp_ranges: []float = null
|
||||
cmp_radar_from: int = 2 # marks of at least this tier are blips; the tracked one always is
|
||||
}
|
||||
|
|
@ -40,8 +42,8 @@ export function compass_config(compass_st: mut CompassState, cap: int) -> void {
|
|||
compass_begin(compass_st)
|
||||
}
|
||||
|
||||
function cmp_new(x: float, z: float, icon: int, colour: int, tier: int, prio: int, label: string) -> CompassMark {
|
||||
let m = new CompassMark
|
||||
# a pooled mark filled in place
|
||||
function cmp_fill(m: CompassMark, x: float, z: float, icon: int, colour: int, tier: int, prio: int, label: string) -> CompassMark {
|
||||
m.x = x
|
||||
m.z = z
|
||||
m.icon = icon
|
||||
|
|
|
|||
|
|
@ -46,8 +46,6 @@ function gpu_select(render3d_st: mut Render3dState) -> int {
|
|||
return render3d_st.gpu_kind
|
||||
}
|
||||
function gpu_backend(render3d_st: Render3dState) -> string { return "vulkan" }
|
||||
# always false now; kept until the game stops asking (maroon-lake src/app/boot_load.ludic)
|
||||
function gpu_is_gl(render3d_st: Render3dState) -> bool { return false }
|
||||
|
||||
# ---- render state ----------------------------------------------------------------
|
||||
# -1 = not known yet: the first set always reaches the driver, so the cache never
|
||||
|
|
|
|||
BIN
packages/ludic.render3d/lib/macos-arm64/libMoltenVK.dylib
(Stored with Git LFS)
Executable file
BIN
packages/ludic.render3d/lib/macos-arm64/libMoltenVK.dylib
(Stored with Git LFS)
Executable file
Binary file not shown.
202
packages/ludic.render3d/native/LICENSE-MoltenVK
Normal file
202
packages/ludic.render3d/native/LICENSE-MoltenVK
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
25
packages/ludic.render3d/native/build.sh
Executable file
25
packages/ludic.render3d/native/build.sh
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
#!/bin/sh
|
||||
# builds lib/macos-arm64/libMoltenVK.dylib: Vulkan over Metal, which render3d draws with on a Mac.
|
||||
# MoltenVK at a pinned tag, its SHA-256 checked; its dependencies (SPIRV-Cross, SPIRV-Tools,
|
||||
# Vulkan-Headers, ...) come at the commits its own ExternalRevisions pins. Needs Xcode (not only the
|
||||
# command line tools); about fifteen minutes. Windows needs nothing: its drivers install the loader.
|
||||
set -eu
|
||||
PKG="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
. "$PKG/../../tools/native/lib.sh"
|
||||
case "$(native_target)" in macos-*) ;; *) echo "ludic.render3d: nothing to build for $(native_target)"; exit 0 ;; esac
|
||||
MVK_TAG=v1.4.2
|
||||
MVK_SHA=6864db532f1dbbdb621a8d0ec13f24edae318fd9269dd3dd0cdff791334bb1cb
|
||||
SRC="$PKG/build/src/moltenvk-$MVK_TAG"
|
||||
native_fetch "$SRC" "https://github.com/KhronosGroup/MoltenVK/archive/refs/tags/$MVK_TAG.tar.gz" "$MVK_SHA"
|
||||
XCODE="${DEVELOPER_DIR:-/Applications/Xcode.app/Contents/Developer}"
|
||||
( cd "$SRC" && DEVELOPER_DIR="$XCODE" ./fetchDependencies --macos && DEVELOPER_DIR="$XCODE" make macos )
|
||||
OUT="$PKG/lib/$(native_target)"
|
||||
mkdir -p "$OUT"
|
||||
# the release is Apple silicon only: the arm64 slice, found by the executable's rpath
|
||||
lipo "$SRC/Package/Release/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" -thin arm64 -output "$OUT/libMoltenVK.dylib"
|
||||
install_name_tool -id @rpath/libMoltenVK.dylib "$OUT/libMoltenVK.dylib" 2>/dev/null
|
||||
# the id change breaks the signature, and Apple silicon loads no unsigned code: signed ad hoc here,
|
||||
# with the Developer ID by ludic bundle / vpk for a release
|
||||
codesign -f -s - "$OUT/libMoltenVK.dylib"
|
||||
cp "$SRC/LICENSE" "$PKG/native/LICENSE-MoltenVK"
|
||||
echo "$OUT/libMoltenVK.dylib"
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
# ludic.render3d — a physically based 3D renderer over Gl.* (OpenGL 4.1 core):
|
||||
# HDRI sky + image-based lighting, a GPU-generated terrain with scanned PBR
|
||||
# materials, cascaded shadow maps, an HDR pipeline with bloom and ACES.
|
||||
# ludic.render3d — a physically based 3D renderer over Vulkan (MoltenVK on a Mac, carried here,
|
||||
# built from a pinned tag by native/build.sh): HDRI sky + image-based lighting, a GPU-generated
|
||||
# terrain with scanned PBR materials, cascaded shadow maps, an HDR pipeline with bloom and ACES.
|
||||
package "ludic.render3d"
|
||||
version "0.1.0"
|
||||
kind source
|
||||
provides "R3d"
|
||||
native "macos-arm64" "lib/macos-arm64/libMoltenVK.dylib"
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ function post_free(render3d_st: mut Render3dState) -> void {
|
|||
# Multisampled scene: 1 (temporal AA alone), 2 or 4, remade at once. Vulkan draws it into
|
||||
# multisampled renderbuffers and resolves them in a pass; a device that cannot take the count asked
|
||||
# for gets the most it can (gpu_msaa_max).
|
||||
function post_msaa_live(render3d_st: Render3dState) -> bool { return gpu_is_gl(render3d_st) or gpu_msaa_max(render3d_st) > 1 }
|
||||
function post_msaa_live(render3d_st: Render3dState) -> bool { return gpu_msaa_max(render3d_st) > 1 }
|
||||
function post_set_msaa(render3d_st: mut Render3dState, n: int) -> void {
|
||||
var want = n
|
||||
if want < 1 { want = 1 }
|
||||
|
|
|
|||
|
|
@ -55,13 +55,15 @@ function el_pct(v: Val) -> float {
|
|||
return 0.0
|
||||
}
|
||||
# the number at the front of `12.5px`
|
||||
function el_num(s: string) -> float {
|
||||
function el_num(s: string) -> float { return el_num_in(s, 0, len(s)) }
|
||||
# the number in s from..to, read where it stands
|
||||
function el_num_in(s: string, from: int, to: int) -> float {
|
||||
var f = 0.0
|
||||
var unit = 0.0
|
||||
var neg = false
|
||||
for i in 0 .. len(s) {
|
||||
for i in from .. to {
|
||||
let c = s[i]
|
||||
if c == 45 and i == 0 { neg = true } else if c == 46 { unit = 0.1 } else if c >= 48 and c <= 57 {
|
||||
if c == 45 and i == from { neg = true } else if c == 46 { unit = 0.1 } else if c >= 48 and c <= 57 {
|
||||
if unit > 0.0 {
|
||||
f = f + float(c - 48) * unit
|
||||
unit = unit * 0.1
|
||||
|
|
|
|||
|
|
@ -34,12 +34,19 @@ function ui_default_backend() -> UiBackend {
|
|||
export state UiState {
|
||||
pl_pools: []UiPool = new []UiPool # pool.ludic: each screen's nodes and envs, used again
|
||||
pl_cur: UiPool = null
|
||||
uv_on: bool = false # pool.ludic: a build is under way, so expressions' Values are pooled
|
||||
uv_true: Val = null
|
||||
uv_false: Val = null
|
||||
mm: UiMemo = null # memo.ludic: splits, gradients and keys by their text
|
||||
mm_ints: []string = null
|
||||
mm_ivals: []Val = null
|
||||
ev_depth: int = 0 # eval.ludic: a call's arguments, in lists kept one a depth
|
||||
ev_args: []Val = new []Val
|
||||
cv_texts: []string = new []string # units_pieces.ludic: a value's var() pieces, kept
|
||||
ca_pool: []CaVal = new []CaVal # calc.ludic: an evaluation's terms, used again by the next
|
||||
ca_used: int = 0
|
||||
ca_args: [][]CaVal = new [][]CaVal
|
||||
ca_depth: int = 0
|
||||
fit_out: []float = new []float # draw_content.ludic: an image's box, kept
|
||||
r3n_cut: []float = new []float # render3d_nine.ludic: a nine-slice's edges, kept
|
||||
r3n_xs: []float = new []float
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ function ca_eval(ui_st: mut UiState, s: string) -> bool {
|
|||
ui_st.ca_s = s
|
||||
ui_st.ca_i = 0
|
||||
ui_st.ca_bad = ""
|
||||
ui_st.ca_used = 0 # this evaluation's terms come from the pool, from the start
|
||||
ui_st.ca_depth = 0
|
||||
let v = ca_factor(ui_st)
|
||||
ca_ws(ui_st)
|
||||
if ui_st.ca_bad == "" and ui_st.ca_i < len(ui_st.ca_s) { ui_st.ca_bad = "nothing after its closing )" }
|
||||
|
|
@ -42,10 +44,10 @@ function ca_sum(ui_st: mut UiState) -> CaVal {
|
|||
}
|
||||
return a
|
||||
}
|
||||
function ca_add(ui_st: UiState, a: CaVal, b: CaVal, minus: bool) -> CaVal {
|
||||
function ca_add(ui_st: mut UiState, a: CaVal, b: CaVal, minus: bool) -> CaVal {
|
||||
var sign = 1.0
|
||||
if minus { sign = -1.0 }
|
||||
let r = new CaVal
|
||||
let r = ca_new(ui_st)
|
||||
if a.bare and b.bare {
|
||||
r.n = a.n + sign * b.n
|
||||
return r
|
||||
|
|
@ -56,10 +58,22 @@ function ca_add(ui_st: UiState, a: CaVal, b: CaVal, minus: bool) -> CaVal {
|
|||
return r
|
||||
}
|
||||
# a bare number beside a length is a length in px
|
||||
function ca_len(ui_st: UiState, a: CaVal) -> CaVal {
|
||||
function ca_len(ui_st: mut UiState, a: CaVal) -> CaVal {
|
||||
if not a.bare { return a }
|
||||
let r = new CaVal
|
||||
let r = ca_new(ui_st)
|
||||
r.bare = false
|
||||
r.px = el_px(ui_st, a.n)
|
||||
return r
|
||||
}
|
||||
# a term, blank, from the pool: an evaluation is over before the next begins, and what it works out is
|
||||
# copied into ca_px / ca_pct, so its records are used again by the next one (never made a frame)
|
||||
function ca_new(ui_st: mut UiState) -> CaVal {
|
||||
if ui_st.ca_used >= len(ui_st.ca_pool) { push(ui_st.ca_pool, new CaVal) }
|
||||
let r = ui_st.ca_pool[ui_st.ca_used]
|
||||
ui_st.ca_used += 1
|
||||
r.px = 0.0
|
||||
r.pct = 0.0
|
||||
r.n = 0.0
|
||||
r.bare = true
|
||||
return r
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,23 +11,28 @@ function ca_fn_at(s: string, at: int) -> string {
|
|||
function ca_word(s: string, at: int, w: string) -> bool { return at + len(w) <= len(s) and sat(s, at, w) }
|
||||
function ca_fn(ui_st: mut UiState, f: string) -> CaVal {
|
||||
ui_st.ca_i = ui_st.ca_i + len(f) + 1
|
||||
let args = new []CaVal
|
||||
let depth = ui_st.ca_depth # min(calc(...)) nests: a kept list of arguments a depth
|
||||
while len(ui_st.ca_args) <= depth { push(ui_st.ca_args, new []CaVal) }
|
||||
let args = ui_st.ca_args[depth]
|
||||
List.clear(args)
|
||||
ui_st.ca_depth = depth + 1
|
||||
while ui_st.ca_bad == "" {
|
||||
push(args, ca_sum(ui_st))
|
||||
ca_ws(ui_st)
|
||||
if ui_st.ca_i < len(ui_st.ca_s) and ui_st.ca_s[ui_st.ca_i] == 44 { ui_st.ca_i += 1 } else { break }
|
||||
}
|
||||
ui_st.ca_depth = depth
|
||||
if ui_st.ca_bad == "" and (ui_st.ca_i >= len(ui_st.ca_s) or ui_st.ca_s[ui_st.ca_i] != 41) { ui_st.ca_bad = "a closing )" }
|
||||
ui_st.ca_i += 1
|
||||
if ui_st.ca_bad != "" { return new CaVal }
|
||||
if ui_st.ca_bad != "" { return ca_new(ui_st) }
|
||||
if f == "clamp" and len(args) != 3 {
|
||||
ui_st.ca_bad = "clamp(least, want, most)"
|
||||
return new CaVal
|
||||
return ca_new(ui_st)
|
||||
}
|
||||
for i in 0 .. len(args) {
|
||||
if args[i].pct != 0.0 {
|
||||
ui_st.ca_bad = `lengths in {f}(), not a percentage`
|
||||
return new CaVal
|
||||
return ca_new(ui_st)
|
||||
}
|
||||
}
|
||||
if f == "clamp" { return ca_pick(ui_st, ca_pick(ui_st, args[1], args[0], false), args[2], true) }
|
||||
|
|
@ -36,7 +41,7 @@ function ca_fn(ui_st: mut UiState, f: string) -> CaVal {
|
|||
return r
|
||||
}
|
||||
# the smaller (or the larger) of two, a bare number beside a length being px
|
||||
function ca_pick(ui_st: UiState, a: CaVal, b: CaVal, smaller: bool) -> CaVal {
|
||||
function ca_pick(ui_st: mut UiState, a: CaVal, b: CaVal, smaller: bool) -> CaVal {
|
||||
if a.bare and b.bare {
|
||||
if (b.n < a.n) == smaller and b.n != a.n { return b }
|
||||
return a
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ function ca_product(ui_st: mut UiState) -> CaVal {
|
|||
ui_st.ca_i += 1
|
||||
let b = ca_factor(ui_st)
|
||||
if div and not b.bare and not a.bare and a.pct == 0.0 and b.pct == 0.0 and b.px != 0.0 {
|
||||
a = ca_ratio(a.px / b.px)
|
||||
a = ca_ratio(ui_st, a.px / b.px)
|
||||
continue
|
||||
}
|
||||
if div and not b.bare {
|
||||
|
|
@ -21,7 +21,7 @@ function ca_product(ui_st: mut UiState) -> CaVal {
|
|||
ui_st.ca_bad = "a number that is not 0 after /"
|
||||
return a
|
||||
}
|
||||
if div { a = ca_scale(a, 1.0 / b.n) } else if a.bare { a = ca_scale(b, a.n) } else if b.bare { a = ca_scale(a, b.n) } else {
|
||||
if div { a = ca_scale(ui_st, a, 1.0 / b.n) } else if a.bare { a = ca_scale(ui_st, b, a.n) } else if b.bare { a = ca_scale(ui_st, a, b.n) } else {
|
||||
ui_st.ca_bad = "a plain number on one side of *"
|
||||
return a
|
||||
}
|
||||
|
|
@ -29,13 +29,13 @@ function ca_product(ui_st: mut UiState) -> CaVal {
|
|||
return a
|
||||
}
|
||||
# a length over a length is a plain number (100vw / 1800px)
|
||||
function ca_ratio(k: float) -> CaVal {
|
||||
let r = new CaVal
|
||||
function ca_ratio(ui_st: mut UiState, k: float) -> CaVal {
|
||||
let r = ca_new(ui_st)
|
||||
r.n = k
|
||||
return r
|
||||
}
|
||||
function ca_scale(a: CaVal, k: float) -> CaVal {
|
||||
let r = new CaVal
|
||||
function ca_scale(ui_st: mut UiState, a: CaVal, k: float) -> CaVal {
|
||||
let r = ca_new(ui_st)
|
||||
r.bare = a.bare
|
||||
r.n = a.n * k
|
||||
r.px = a.px * k
|
||||
|
|
@ -46,12 +46,12 @@ function ca_factor(ui_st: mut UiState) -> CaVal {
|
|||
ca_ws(ui_st)
|
||||
if ui_st.ca_i >= len(ui_st.ca_s) {
|
||||
ui_st.ca_bad = "a value"
|
||||
return new CaVal
|
||||
return ca_new(ui_st)
|
||||
}
|
||||
let c = ui_st.ca_s[ui_st.ca_i]
|
||||
if c == 45 {
|
||||
ui_st.ca_i += 1
|
||||
return ca_scale(ca_factor(ui_st), -1.0)
|
||||
return ca_scale(ui_st, ca_factor(ui_st), -1.0)
|
||||
}
|
||||
let f = ca_fn_at(ui_st.ca_s, ui_st.ca_i)
|
||||
if f == "min" or f == "max" or f == "clamp" { return ca_fn(ui_st, f) }
|
||||
|
|
@ -71,18 +71,21 @@ function ca_number(ui_st: mut UiState) -> CaVal {
|
|||
while ui_st.ca_i < len(ui_st.ca_s) and ((ui_st.ca_s[ui_st.ca_i] >= 48 and ui_st.ca_s[ui_st.ca_i] <= 57) or ui_st.ca_s[ui_st.ca_i] == 46) { ui_st.ca_i += 1 }
|
||||
let num_end = ui_st.ca_i
|
||||
while ui_st.ca_i < len(ui_st.ca_s) and ((ui_st.ca_s[ui_st.ca_i] >= 97 and ui_st.ca_s[ui_st.ca_i] <= 122) or ui_st.ca_s[ui_st.ca_i] == 37) { ui_st.ca_i += 1 }
|
||||
let r = new CaVal
|
||||
let r = ca_new(ui_st)
|
||||
if num_end == at {
|
||||
ui_st.ca_bad = "a number"
|
||||
return r
|
||||
}
|
||||
let word: string = ui_st.ca_s[at..ui_st.ca_i]
|
||||
let unit: string = ui_st.ca_s[num_end..ui_st.ca_i]
|
||||
if unit == "" {
|
||||
r.n = el_num(word)
|
||||
let s = ui_st.ca_s
|
||||
let end = ui_st.ca_i
|
||||
let f = el_num_in(s, at, num_end)
|
||||
if end == num_end {
|
||||
r.n = f
|
||||
return r
|
||||
}
|
||||
r.bare = false
|
||||
if unit == "%" { r.pct = el_num(word) } else if unit == "px" or unit == "em" or unit == "rem" or unit == "vw" or unit == "vh" { r.px = el_unit(ui_st, word) } else { ui_st.ca_bad = `a unit calc() knows, not {unit}` }
|
||||
if ca_unit(s, num_end, end, "%") { r.pct = f } else if ca_unit(s, num_end, end, "px") { r.px = el_px(ui_st, f) } else if ca_unit(s, num_end, end, "rem") { r.px = f * el_px(ui_st, 16.0) } else if ca_unit(s, num_end, end, "em") { r.px = f * el_em(ui_st) } else if ca_unit(s, num_end, end, "vw") { r.px = f * ui_st.ly_vp_w / 100.0 } else if ca_unit(s, num_end, end, "vh") { r.px = f * ui_st.ly_vp_h / 100.0 } else { ui_st.ca_bad = `a unit calc() knows, not {s[num_end..end]}` }
|
||||
return r
|
||||
}
|
||||
# the unit between `from` and `to` is `u`, compared in place
|
||||
function ca_unit(s: string, from: int, to: int, u: string) -> bool { return to - from == len(u) and sat(s, from, u) }
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ function ct_sound(n: UiNode) -> string {
|
|||
function ct_activate(ui_st: mut UiState, n: UiNode) -> void {
|
||||
if not n.enabled { return }
|
||||
let t = ct_type(n)
|
||||
if t == "checkbox" { ct_change(ui_st, n, vl_bool(not ui_attr_on(n, "checked"))) } else if t == "radio" { ct_change(ui_st, n, ui_attr(n, "value")) } else if t == "select" { ct_step(ui_st, n, 1) } else if t == "key" { ct_key_start(ui_st, n) } else if t == "number" { ct_num_commit(ui_st, n) } else if t == "text" { ct_submit(ui_st, n) } else if t == "" {
|
||||
if t == "checkbox" { ct_change(ui_st, n, vl_bool(ui_st, not ui_attr_on(n, "checked"))) } else if t == "radio" { ct_change(ui_st, n, ui_attr(n, "value")) } else if t == "select" { ct_step(ui_st, n, 1) } else if t == "key" { ct_key_start(ui_st, n) } else if t == "number" { ct_num_commit(ui_st, n) } else if t == "text" { ct_submit(ui_st, n) } else if t == "" {
|
||||
ui_sound(ui_st, ct_sound(n))
|
||||
ui_fire(ui_st, n, "press", Value.null())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,21 +5,21 @@ function ui_eval(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> Val {
|
|||
if x.op == X_MEMBER {
|
||||
let o = ui_eval(ui_st, x.a, e)
|
||||
if Value.kind(o) == 6 and Value.has(o, x.s) != 0 { return Value.get(o, x.s) }
|
||||
if Value.kind(o) == 5 and x.s == "length" { return Value.int(Value.count(o)) }
|
||||
if Value.kind(o) == 5 and x.s == "length" { return uv_int(ui_st, Value.count(o)) }
|
||||
ui_err(ui_st, `.{x.s}: there is no such field`)
|
||||
return Value.null()
|
||||
}
|
||||
if x.op == X_INDEX { return Value.at(ui_eval(ui_st, x.a, e), Value.as_int(ui_eval(ui_st, x.b, e))) }
|
||||
if x.op == X_CALL { return ev_call(ui_st, x, e) }
|
||||
if x.op == X_NOT { return vl_bool(not vl_truthy(ui_eval(ui_st, x.a, e))) }
|
||||
if x.op == X_NEG { return vl_arith("-", Value.int(0), ui_eval(ui_st, x.a, e)) }
|
||||
if x.op == X_NOT { return vl_bool(ui_st, not vl_truthy(ui_eval(ui_st, x.a, e))) }
|
||||
if x.op == X_NEG { return vl_arith(ui_st, "-", mm_ival(ui_st, 0), ui_eval(ui_st, x.a, e)) }
|
||||
if x.op == X_AND {
|
||||
if not vl_truthy(ui_eval(ui_st, x.a, e)) { return vl_bool(false) }
|
||||
return vl_bool(vl_truthy(ui_eval(ui_st, x.b, e)))
|
||||
if not vl_truthy(ui_eval(ui_st, x.a, e)) { return vl_bool(ui_st, false) }
|
||||
return vl_bool(ui_st, vl_truthy(ui_eval(ui_st, x.b, e)))
|
||||
}
|
||||
if x.op == X_OR {
|
||||
if vl_truthy(ui_eval(ui_st, x.a, e)) { return vl_bool(true) }
|
||||
return vl_bool(vl_truthy(ui_eval(ui_st, x.b, e)))
|
||||
if vl_truthy(ui_eval(ui_st, x.a, e)) { return vl_bool(ui_st, true) }
|
||||
return vl_bool(ui_st, vl_truthy(ui_eval(ui_st, x.b, e)))
|
||||
}
|
||||
if x.op == X_COND {
|
||||
if vl_truthy(ui_eval(ui_st, x.a, e)) { return ui_eval(ui_st, x.b, e) }
|
||||
|
|
@ -27,10 +27,10 @@ function ui_eval(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> Val {
|
|||
}
|
||||
if x.op == X_TEXT {
|
||||
var out = ""
|
||||
for i in 0 .. len(x.args) { out = out + vl_text(ui_eval(ui_st, x.args[i], e)) }
|
||||
return Value.str(out)
|
||||
for i in 0 .. len(x.args) { out = out + vl_text_u(ui_st, ui_eval(ui_st, x.args[i], e)) }
|
||||
return uv_str(ui_st, out)
|
||||
}
|
||||
return vl_binary(x.s, ui_eval(ui_st, x.a, e), ui_eval(ui_st, x.b, e))
|
||||
return vl_binary(ui_st, x.s, ui_eval(ui_st, x.a, e), ui_eval(ui_st, x.b, e))
|
||||
}
|
||||
# a call: len() and range() are the template's own, anything else is the view's
|
||||
function ev_call(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> Val {
|
||||
|
|
@ -46,10 +46,10 @@ function ev_call(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> Val {
|
|||
}
|
||||
# the call itself, its arguments evaluated; a callee reads them and keeps none
|
||||
function ev_call_with(ui_st: mut UiState, x: UiExpr, e: UiEnv, args: Val) -> Val {
|
||||
if x.s == "len" and len(x.args) == 1 { return Value.int(Value.count(Value.at(args, 0))) }
|
||||
if x.s == "len" and len(x.args) == 1 { return uv_int(ui_st, Value.count(Value.at(args, 0))) }
|
||||
if x.s == "range" and len(x.args) == 1 {
|
||||
let out = Value.list()
|
||||
for i in 0 .. Value.as_int(Value.at(args, 0)) { Value.add(out, Value.int(i)) }
|
||||
let out = uv_list(ui_st)
|
||||
for i in 0 .. Value.as_int(Value.at(args, 0)) { Value.add(out, uv_int(ui_st, i)) }
|
||||
return out
|
||||
}
|
||||
var at = e
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export function ui_nodes(ui_st: mut UiState, screen: string, view: UiView) -> Ui
|
|||
}
|
||||
let t = ui_st.lib_screens[si]
|
||||
pl_begin(ui_st, screen)
|
||||
ui_st.uv_on = true
|
||||
lc_begin(ui_st)
|
||||
ui_st.ui_view = view
|
||||
let e = env_new(ui_st, null)
|
||||
|
|
@ -55,6 +56,7 @@ export function ui_nodes(ui_st: mut UiState, screen: string, view: UiView) -> Ui
|
|||
}
|
||||
bd_kids(ui_st, t.kids, e, screen, root.children)
|
||||
cs_tree(ui_st, root)
|
||||
ui_st.uv_on = false
|
||||
return root
|
||||
}
|
||||
# the room vw, vh and @media measure against; ui_show sets it to the screen's rectangle
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ export property UiPool {
|
|||
envs1: []UiEnv = null
|
||||
un: int = 0
|
||||
ue: int = 0
|
||||
vals0: []Val = null # what expressions work out while the screen is built (uv_new)
|
||||
vals1: []Val = null
|
||||
uv: int = 0
|
||||
}
|
||||
# a build of `screen` begins: its other generation is handed out again from the start
|
||||
function pl_begin(ui_st: mut UiState, screen: string) -> void {
|
||||
|
|
@ -26,11 +29,14 @@ function pl_begin(ui_st: mut UiState, screen: string) -> void {
|
|||
p.nodes1 = new []UiNode
|
||||
p.envs0 = new []UiEnv
|
||||
p.envs1 = new []UiEnv
|
||||
p.vals0 = new []Val
|
||||
p.vals1 = new []Val
|
||||
push(ui_st.pl_pools, p)
|
||||
}
|
||||
p.gen = 1 - p.gen
|
||||
p.un = 0
|
||||
p.ue = 0
|
||||
p.uv = 0
|
||||
ui_st.pl_cur = p
|
||||
}
|
||||
# a node, blank, with its own lists (empty) - from the pool when a build is under way
|
||||
|
|
@ -82,3 +88,37 @@ function env_fresh() -> UiEnv {
|
|||
e.on_envs = new []UiEnv
|
||||
return e
|
||||
}
|
||||
# a Value an expression works out while a screen is built: from the pool while the build runs (none
|
||||
# outlives the screen's next build but one, like its nodes), made as ever outside one - an action's
|
||||
# `set`, a <state>'s start, what a handler is given are kept, so they are never pooled
|
||||
function uv_new(ui_st: mut UiState, tag: int) -> Val {
|
||||
let p = ui_st.pl_cur
|
||||
if not ui_st.uv_on or p == null { return Value.become(Value.null(), tag) }
|
||||
var list = p.vals0
|
||||
if p.gen == 1 { list = p.vals1 }
|
||||
if p.uv < len(list) {
|
||||
let v = list[p.uv]
|
||||
p.uv += 1
|
||||
return Value.become(v, tag)
|
||||
}
|
||||
let v = Value.become(Value.null(), tag)
|
||||
push(list, v)
|
||||
p.uv += 1
|
||||
return v
|
||||
}
|
||||
function uv_int(ui_st: mut UiState, n: int) -> Val {
|
||||
if n >= 0 and n < 1024 { return mm_ival(ui_st, n) }
|
||||
return Value.be_int(uv_new(ui_st, 1), n)
|
||||
}
|
||||
function uv_float(ui_st: mut UiState, x: float) -> Val { return Value.be_float(uv_new(ui_st, 7), x) }
|
||||
function uv_str(ui_st: mut UiState, s: string) -> Val { return Value.be_str(uv_new(ui_st, 4), s) }
|
||||
function uv_list(ui_st: mut UiState) -> Val { return uv_new(ui_st, 5) }
|
||||
# true and false, one of each for good: read them, never change them
|
||||
function uv_bool(ui_st: mut UiState, b: bool) -> Val {
|
||||
if ui_st.uv_true == null {
|
||||
ui_st.uv_true = Value.bool(1)
|
||||
ui_st.uv_false = Value.bool(0)
|
||||
}
|
||||
if b { return ui_st.uv_true }
|
||||
return ui_st.uv_false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
# a Ludic component shown as a screen: an instance at the top, keyed by its name
|
||||
function fr_class_nodes(ui_st: mut UiState, cls: UiClass) -> UiNode {
|
||||
pl_begin(ui_st, cls.name)
|
||||
ui_st.uv_on = true
|
||||
lc_begin(ui_st)
|
||||
ui_st.in_build += 1
|
||||
ui_st.ui_view = null
|
||||
|
|
@ -26,6 +27,7 @@ function fr_class_nodes(ui_st: mut UiState, cls: UiClass) -> UiNode {
|
|||
bd_class(ui_st, use, e, cls.name, root.children)
|
||||
in_sweep(ui_st, cls.name)
|
||||
cs_tree(ui_st, root)
|
||||
ui_st.uv_on = false
|
||||
return root
|
||||
}
|
||||
function fr_root(ui_st: mut UiState, key: string, sheet: UiSheet) -> UiNode {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ function st_for(ui_st: mut UiState, key: string, t: UiTpl, e: UiEnv) -> Val {
|
|||
if ui_st.st_keys[i] == key { return ui_st.st_vals[i] }
|
||||
}
|
||||
let o = Value.object()
|
||||
let was = ui_st.uv_on # a state's start is kept for good: its values are never pooled
|
||||
ui_st.uv_on = false
|
||||
for i in 0 .. len(decl.keys) { Value.put(o, decl.keys[i], ui_eval(ui_st, decl.vals[i], e)) }
|
||||
ui_st.uv_on = was
|
||||
push(ui_st.st_keys, key)
|
||||
push(ui_st.st_vals, o)
|
||||
return o
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
# units.ludic - lengths in px (the renderer's scale), em (the font size here), rem (the root's),
|
||||
# vw and vh (the screen's), and custom properties: --name set on an element, read by var(--name)
|
||||
# in it and everything inside it
|
||||
# the px of one em for the element being styled (a font size's em is its parent's)
|
||||
function el_em(ui_st: UiState) -> float {
|
||||
if ui_st.el_cur == null { return el_px(ui_st, 16.0) }
|
||||
if ui_st.el_key == "size" and ui_st.el_cur.parent != null { return cs_fs(ui_st, ui_st.el_cur.parent) }
|
||||
return cs_fs(ui_st, ui_st.el_cur)
|
||||
}
|
||||
function el_unit(ui_st: UiState, s: string) -> float {
|
||||
let f = el_num(s)
|
||||
let n = len(s)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
# values.ludic - arithmetic, comparison, truth and text over a template's values. A sum of two
|
||||
# whole numbers stays whole; anything with a fraction in it is a float; a string in a sum joins.
|
||||
function vl_bool(b: bool) -> Val {
|
||||
if b { return Value.bool(1) }
|
||||
return Value.bool(0)
|
||||
}
|
||||
function vl_bool(ui_st: mut UiState, b: bool) -> Val { return uv_bool(ui_st, b) }
|
||||
function vl_num(v: Val) -> bool { return Value.kind(v) == 1 or Value.kind(v) == 2 or Value.kind(v) == 7 }
|
||||
function vl_truthy(v: Val) -> bool {
|
||||
let k = Value.kind(v)
|
||||
|
|
@ -29,25 +26,25 @@ function vl_text(v: Val) -> string {
|
|||
}
|
||||
return Json.encode(v)
|
||||
}
|
||||
function vl_arith(op: string, a: Val, b: Val) -> Val {
|
||||
function vl_arith(ui_st: mut UiState, op: string, a: Val, b: Val) -> Val {
|
||||
if Value.kind(a) == 1 and Value.kind(b) == 1 {
|
||||
let x = Value.as_int(a)
|
||||
let y = Value.as_int(b)
|
||||
if op == "+" { return Value.int(x + y) }
|
||||
if op == "-" { return Value.int(x - y) }
|
||||
if op == "*" { return Value.int(x * y) }
|
||||
if y == 0 { return Value.int(0) }
|
||||
if op == "/" { return Value.int(x / y) }
|
||||
return Value.int(x % y)
|
||||
if op == "+" { return uv_int(ui_st, x + y) }
|
||||
if op == "-" { return uv_int(ui_st, x - y) }
|
||||
if op == "*" { return uv_int(ui_st, x * y) }
|
||||
if y == 0 { return uv_int(ui_st, 0) }
|
||||
if op == "/" { return uv_int(ui_st, x / y) }
|
||||
return uv_int(ui_st, x % y)
|
||||
}
|
||||
let x = Value.as_float(a)
|
||||
let y = Value.as_float(b)
|
||||
if op == "+" { return Value.float(x + y) }
|
||||
if op == "-" { return Value.float(x - y) }
|
||||
if op == "*" { return Value.float(x * y) }
|
||||
if y == 0.0 { return Value.float(0.0) }
|
||||
if op == "/" { return Value.float(x / y) }
|
||||
return Value.float(x - y * float(int(x / y)))
|
||||
if op == "+" { return uv_float(ui_st, x + y) }
|
||||
if op == "-" { return uv_float(ui_st, x - y) }
|
||||
if op == "*" { return uv_float(ui_st, x * y) }
|
||||
if y == 0.0 { return uv_float(ui_st, 0.0) }
|
||||
if op == "/" { return uv_float(ui_st, x / y) }
|
||||
return uv_float(ui_st, x - y * float(int(x / y)))
|
||||
}
|
||||
function vl_equal(a: Val, b: Val) -> bool {
|
||||
if vl_num(a) and vl_num(b) { return Value.as_float(a) == Value.as_float(b) }
|
||||
|
|
@ -56,17 +53,17 @@ function vl_equal(a: Val, b: Val) -> bool {
|
|||
if Value.kind(a) == 3 { return Value.as_int(a) == Value.as_int(b) }
|
||||
return Value.kind(a) == 0
|
||||
}
|
||||
function vl_binary(op: string, a: Val, b: Val) -> Val {
|
||||
if op == "==" { return vl_bool(vl_equal(a, b)) }
|
||||
if op == "!=" { return vl_bool(not vl_equal(a, b)) }
|
||||
if op == "+" and (Value.kind(a) == 4 or Value.kind(b) == 4) { return Value.str(vl_text(a) + vl_text(b)) }
|
||||
if op == "+" or op == "-" or op == "*" or op == "/" or op == "%" { return vl_arith(op, a, b) }
|
||||
function vl_binary(ui_st: mut UiState, op: string, a: Val, b: Val) -> Val {
|
||||
if op == "==" { return vl_bool(ui_st, vl_equal(a, b)) }
|
||||
if op == "!=" { return vl_bool(ui_st, not vl_equal(a, b)) }
|
||||
if op == "+" and (Value.kind(a) == 4 or Value.kind(b) == 4) { return uv_str(ui_st, vl_text_u(ui_st, a) + vl_text_u(ui_st, b)) }
|
||||
if op == "+" or op == "-" or op == "*" or op == "/" or op == "%" { return vl_arith(ui_st, op, a, b) }
|
||||
let x = Value.as_float(a)
|
||||
let y = Value.as_float(b)
|
||||
if op == "<" { return vl_bool(x < y) }
|
||||
if op == "<=" { return vl_bool(x <= y) }
|
||||
if op == ">" { return vl_bool(x > y) }
|
||||
return vl_bool(x >= y)
|
||||
if op == "<" { return vl_bool(ui_st, x < y) }
|
||||
if op == "<=" { return vl_bool(ui_st, x <= y) }
|
||||
if op == ">" { return vl_bool(ui_st, x > y) }
|
||||
return vl_bool(ui_st, x >= y)
|
||||
}
|
||||
# a value as text, whatever kind it is: a component's string prop given a number (label="{3}") reads
|
||||
# "3", not nothing
|
||||
|
|
|
|||
|
|
@ -480,6 +480,10 @@ namespace Value {
|
|||
alias as_str(value) = value_as_str
|
||||
alias free(value) = value_free
|
||||
alias clear(value) = value_clear
|
||||
alias become(value, tag) = value_become
|
||||
alias be_int(value, n) = value_be_int
|
||||
alias be_float(value, x) = value_be_float
|
||||
alias be_str(value, s) = value_be_str
|
||||
}
|
||||
namespace Json {
|
||||
alias encode(value) = json_encode
|
||||
|
|
|
|||
|
|
@ -105,6 +105,79 @@ function value_set_bool(o: Val, key: pointer, b: bool) -> void {
|
|||
v.num = 0
|
||||
if b { v.num = 1 }
|
||||
}
|
||||
# a list field of an object set in place: the list under `key` is kept, and item i keeps its Value when it
|
||||
# is already that kind (a component's model, filled into the same object every frame)
|
||||
function value_list_fit(o: Val, key: pointer, n: int) -> Val {
|
||||
let l = value_slot(o, key, 5)
|
||||
value_lists(l)
|
||||
while len(l.kids) > n { List.pop(l.kids) }
|
||||
return l
|
||||
}
|
||||
function value_item(l: Val, i: int, tag: int) -> Val {
|
||||
if i < len(l.kids) {
|
||||
if l.kids[i].tag == tag { return l.kids[i] }
|
||||
let v = value_new(tag)
|
||||
l.kids[i] = v
|
||||
return v
|
||||
}
|
||||
let v = value_new(tag)
|
||||
push(l.kids, v)
|
||||
return v
|
||||
}
|
||||
function value_set_ints(o: Val, key: pointer, xs: []int) -> void {
|
||||
let l = value_list_fit(o, key, len(xs))
|
||||
for i in 0 .. len(xs) {
|
||||
let v = value_item(l, i, 1)
|
||||
v.num = xs[i]
|
||||
}
|
||||
}
|
||||
function value_set_floats(o: Val, key: pointer, xs: []float) -> void {
|
||||
let l = value_list_fit(o, key, len(xs))
|
||||
for i in 0 .. len(xs) {
|
||||
let v = value_item(l, i, 7)
|
||||
v.num = float_bits(xs[i])
|
||||
}
|
||||
}
|
||||
function value_set_strs(o: Val, key: pointer, xs: []string) -> void {
|
||||
let l = value_list_fit(o, key, len(xs))
|
||||
for i in 0 .. len(xs) {
|
||||
let v = value_item(l, i, 4)
|
||||
v.txt = xs[i]
|
||||
}
|
||||
}
|
||||
function value_set_bools(o: Val, key: pointer, xs: []bool) -> void {
|
||||
let l = value_list_fit(o, key, len(xs))
|
||||
for i in 0 .. len(xs) {
|
||||
let v = value_item(l, i, 3)
|
||||
v.num = 0
|
||||
if xs[i] { v.num = 1 }
|
||||
}
|
||||
}
|
||||
# a Value turned into a blank one of kind `tag`, its own lists (if it has them) kept and emptied - for
|
||||
# a pool that hands the same records out again (ludic.ui's, frame after frame)
|
||||
function value_become(v: Val, tag: int) -> Val {
|
||||
v.tag = tag
|
||||
v.num = 0
|
||||
v.txt = null
|
||||
if tag == 5 or tag == 6 { value_lists(v) }
|
||||
if v.kids != null { value_clear(v) }
|
||||
return v
|
||||
}
|
||||
function value_be_int(v: Val, n: int) -> Val {
|
||||
value_become(v, 1)
|
||||
v.num = n
|
||||
return v
|
||||
}
|
||||
function value_be_float(v: Val, x: float) -> Val {
|
||||
value_become(v, 7)
|
||||
v.num = float_bits(x)
|
||||
return v
|
||||
}
|
||||
function value_be_str(v: Val, s: pointer) -> Val {
|
||||
value_become(v, 4)
|
||||
v.txt = s
|
||||
return v
|
||||
}
|
||||
# a list or an object emptied in place, its room kept - for one filled again every frame
|
||||
function value_clear(v: Val) -> void {
|
||||
if v.kids == null { return }
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ declare i32 @lvk_bind()
|
|||
@.lvk_fmt = private unnamed_addr constant [25 x i8] c"%s/lib/libvulkan.1.dylib\00"
|
||||
@.lvk_fmtm = private unnamed_addr constant [25 x i8] c"%s/lib/libMoltenVK.dylib\00"
|
||||
@.lvk_m0 = private unnamed_addr constant [49 x i8] c"@executable_path/../Frameworks/libMoltenVK.dylib\00"
|
||||
@.lvk_mr = private unnamed_addr constant [25 x i8] c"@rpath/libMoltenVK.dylib\00"
|
||||
@.lvk_m1 = private unnamed_addr constant [35 x i8] c"@executable_path/libMoltenVK.dylib\00"
|
||||
@.lvk_m2 = private unnamed_addr constant [18 x i8] c"libMoltenVK.dylib\00"
|
||||
@lvk_lib = internal global ptr null
|
||||
|
|
@ -40,8 +41,9 @@ entry:
|
|||
|
||||
; the candidates in order: a Vulkan loader first (the SDK's, so the validation layer can be
|
||||
; stacked in development), then MoltenVK itself, which exports every vk* entry point and is what
|
||||
; a bundle ships in Contents/Frameworks - no loader, no ICD json.
|
||||
@lvk_paths = internal constant [7 x ptr] [ptr @.lvk_c0, ptr @.lvk_c1, ptr @.lvk_c2, ptr @.lvk_c3, ptr @.lvk_m0, ptr @.lvk_m1, ptr @.lvk_m2]
|
||||
; a bundle ships in Contents/Frameworks - no loader, no ICD json - or, found through the
|
||||
; executable's rpath, what ludic.render3d carries (lib/macos-arm64) for every other build.
|
||||
@lvk_paths = internal constant [8 x ptr] [ptr @.lvk_c0, ptr @.lvk_c1, ptr @.lvk_c2, ptr @.lvk_c3, ptr @.lvk_m0, ptr @.lvk_mr, ptr @.lvk_m1, ptr @.lvk_m2]
|
||||
|
||||
define i32 @lvk_open() {
|
||||
entry:
|
||||
|
|
@ -50,14 +52,14 @@ entry:
|
|||
br i1 %open, label %ok, label %loop
|
||||
loop:
|
||||
%i = phi i32 [ 0, %entry ], [ %i1, %next ]
|
||||
%slot = getelementptr [7 x ptr], ptr @lvk_paths, i32 0, i32 %i
|
||||
%slot = getelementptr [8 x ptr], ptr @lvk_paths, i32 0, i32 %i
|
||||
%path = load ptr, ptr %slot
|
||||
%h = call ptr @lvk_try(ptr %path)
|
||||
%miss = icmp eq ptr %h, null
|
||||
br i1 %miss, label %next, label %got
|
||||
next:
|
||||
%i1 = add i32 %i, 1
|
||||
%more = icmp slt i32 %i1, 7
|
||||
%more = icmp slt i32 %i1, 8
|
||||
br i1 %more, label %loop, label %sdk
|
||||
got:
|
||||
store ptr %h, ptr @lvk_lib
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ function cm_set(c: int, ty: pointer, k: pointer, x: pointer, head: Node) -> poin
|
|||
if (ty == "float") { return ` value_set_float(o, "{k}", {x})\n` }
|
||||
if (ty == "string") { return ` value_set_str(o, "{k}", {x})\n` }
|
||||
if (ty == "bool") { return ` value_set_bool(o, "{k}", {x})\n` }
|
||||
if (ty == "[]int") { return ` value_set_ints(o, "{k}", {x})\n` }
|
||||
if (ty == "[]float") { return ` value_set_floats(o, "{k}", {x})\n` }
|
||||
if (ty == "[]string") { return ` value_set_strs(o, "{k}", {x})\n` }
|
||||
if (ty == "[]bool") { return ` value_set_bools(o, "{k}", {x})\n` }
|
||||
return ` value_put(o, "{k}", {cm_to(c, ty, x, head)})\n`
|
||||
}
|
||||
function cm_to(c: int, ty: pointer, x: pointer, head: Node) -> pointer {
|
||||
|
|
|
|||
44538
selfhost/ludicc.seed.ll
44538
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -455,7 +455,7 @@ function lab_plate_case() -> void {
|
|||
# from the checkout's root, where the renderer's shaders and the plate are found beside it
|
||||
let work = `{tmp_dir()}/labrun`
|
||||
shell(`rm -rf {work} build/lab/probe && mkdir -p {work}`)
|
||||
if not shq(`yes '' | head -300 | {vk_env()}/usr/bin/time -l {bin} > {work}/run.out 2> {work}/time.out`) { bad2(lbl, capture_line(`tail -1 {work}/run.out`)); return }
|
||||
if not shq(`yes '' | head -300 | /usr/bin/time -l {bin} > {work}/run.out 2> {work}/time.out`) { bad2(lbl, capture_line(`tail -1 {work}/run.out`)); return }
|
||||
var n = 0
|
||||
let names = ["front", "side", "above"]
|
||||
for i in 0 .. 3 {
|
||||
|
|
@ -608,22 +608,12 @@ function headless_case(path: pointer, exp: pointer, label: pointer) -> void {
|
|||
let got = capture_line(`{out} < /dev/null`)
|
||||
if got == exp { ok(label) } else { bad2(label, `got [{got}] want [{exp}]`) }
|
||||
}
|
||||
# render3d draws with Vulkan only: on a Mac with no loader beside the binary, a run finds MoltenVK
|
||||
# through the SDK - VULKAN_SDK as set, else the newest ~/VulkanSDK/<version>/macOS
|
||||
function vk_env() -> string {
|
||||
let set = Os.env("VULKAN_SDK")
|
||||
if set != null and set != "" { return "" }
|
||||
let found = capture_line("ls -d $HOME/VulkanSDK/*/macOS 2>/dev/null | tail -1")
|
||||
if len(found) == 0 { return "" }
|
||||
# the SDK's loader finds MoltenVK only through its driver manifest
|
||||
return `VULKAN_SDK={found} VK_ICD_FILENAMES={found}/share/vulkan/icd.d/MoltenVK_icd.json `
|
||||
}
|
||||
# a program built headless whose verdict is one of its lines (the renderer logs before it)
|
||||
function headless_line_case(path: pointer, exp: pointer, label: pointer) -> void {
|
||||
let out = `{tmp_dir()}/h_{flat(path)}`
|
||||
if not shq(`bin/ludicc --headless examples/{path}.ludic -o {out} > {out}.log 2>&1`) { bad2(label, capture_line(`grep -i error {out}.log | head -1`)); return }
|
||||
let got = capture_line(`{vk_env()}{out} < /dev/null 2>&1 | grep -c '^{exp}$'`)
|
||||
if got == "1" { ok(label) } else { bad2(label, capture_line(`{vk_env()}{out} < /dev/null 2>&1 | grep -i -E 'fail|cannot' | head -1`)) }
|
||||
let got = capture_line(`{out} < /dev/null 2>&1 | grep -c '^{exp}$'`)
|
||||
if got == "1" { ok(label) } else { bad2(label, capture_line(`{out} < /dev/null 2>&1 | grep -i -E 'fail|cannot' | head -1`)) }
|
||||
}
|
||||
# ludic migrate state and components: the header names what every member needs, a field read in a
|
||||
# member is not edited, a module named like a package keeps a state of its own, and the program
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue