working on fixing dialing issue with http library
This commit is contained in:
@@ -16,13 +16,25 @@ RESOURCE_DIR := $(CURDIR)/resources/dev
|
|||||||
GUI_ENV_FILE ?= $(RESOURCE_DIR)/gui.env.example
|
GUI_ENV_FILE ?= $(RESOURCE_DIR)/gui.env.example
|
||||||
API_ENV_FILE ?= $(RESOURCE_DIR)/api.env.example
|
API_ENV_FILE ?= $(RESOURCE_DIR)/api.env.example
|
||||||
API_DESI_DATA ?= $(RESOURCE_DIR)/desi_subset.json
|
API_DESI_DATA ?= $(RESOURCE_DIR)/desi_subset.json
|
||||||
# The API binds here for local dev (see api/src/config.rs); `make run` passes
|
# The API binds here for local dev (see api/src/config.rs); `make run` waits
|
||||||
# it through and waits for $(API_HEALTH_URL) to respond before launching the
|
# for $(API_HEALTH_URL) to respond before launching the GUI, so the renderer
|
||||||
# GUI, so the renderer never races the API on startup. A bind host of
|
# never races the API on startup. A bind host of 0.0.0.0 is probed via
|
||||||
# 0.0.0.0 is probed via 127.0.0.1. Adjust API_WAIT_TIMEOUT (seconds) if the
|
# 127.0.0.1. Adjust API_WAIT_TIMEOUT (seconds) if the API takes longer than
|
||||||
# API takes longer than 60s to become healthy on a given machine.
|
# 60s to become healthy on a given machine.
|
||||||
|
#
|
||||||
|
# API_BIND_ADDR is only forwarded to the API when the user sets it explicitly
|
||||||
|
# (command line / environment). When it is left unset, the API reads it from
|
||||||
|
# $(API_ENV_FILE) (dotenvy applies env-file values that aren't already in the
|
||||||
|
# process environment), so the Makefile must not inject its own default here
|
||||||
|
# or it would shadow the env file. The effective bind address is read back
|
||||||
|
# from $(API_ENV_FILE) so the health check always polls the right port.
|
||||||
API_BIND_ADDR ?= 127.0.0.1:8080
|
API_BIND_ADDR ?= 127.0.0.1:8080
|
||||||
API_HEALTH_URL := http://$(subst 0.0.0.0,127.0.0.1,$(API_BIND_ADDR))/health
|
API_BIND_EXPLICIT := $(filter command line environment,$(origin API_BIND_ADDR))
|
||||||
|
API_BIND_FROM_FILE := $(if $(API_BIND_EXPLICIT),,$(shell \
|
||||||
|
grep -E '^[[:space:]]*API_BIND_ADDR[[:space:]]*=' '$(API_ENV_FILE)' 2>/dev/null | \
|
||||||
|
tail -n 1 | sed 's/^[[:space:]]*API_BIND_ADDR[[:space:]]*=[[:space:]]*//; s/[[:space:]]*#.*$$//'))
|
||||||
|
API_BIND_EFFECTIVE := $(if $(API_BIND_EXPLICIT),$(API_BIND_ADDR),$(or $(API_BIND_FROM_FILE),$(API_BIND_ADDR)))
|
||||||
|
API_HEALTH_URL := http://$(subst 0.0.0.0,127.0.0.1,$(API_BIND_EFFECTIVE))/health
|
||||||
API_WAIT_TIMEOUT ?= 60
|
API_WAIT_TIMEOUT ?= 60
|
||||||
|
|
||||||
.PHONY: help setup run run-web build build-debug build-web test clean fmt \
|
.PHONY: help setup run run-web build build-debug build-web test clean fmt \
|
||||||
@@ -48,7 +60,7 @@ setup: ## Setup all sub-projects (submodules, gui deps, api deps, infra deps)
|
|||||||
## ---- Renderer (Odin) -----------------------------------------------------
|
## ---- Renderer (Odin) -----------------------------------------------------
|
||||||
|
|
||||||
run: ## Run the native app (gui/); waits for the API to be healthy first
|
run: ## Run the native app (gui/); waits for the API to be healthy first
|
||||||
@API_ENV_FILE="$(API_ENV_FILE)" API_DESI_DATA="$(API_DESI_DATA)" API_BIND_ADDR="$(API_BIND_ADDR)" $(MAKE) -C $(API) run & api_pid=$$!; \
|
@API_ENV_FILE="$(API_ENV_FILE)" API_DESI_DATA="$(API_DESI_DATA)" $(if $(API_BIND_EXPLICIT),API_BIND_ADDR="$(API_BIND_ADDR)",) $(MAKE) -C $(API) run & api_pid=$$!; \
|
||||||
trap 'kill $$api_pid 2>/dev/null' INT TERM EXIT; \
|
trap 'kill $$api_pid 2>/dev/null' INT TERM EXIT; \
|
||||||
$(MAKE) api-wait || { kill $$api_pid 2>/dev/null; exit 1; }; \
|
$(MAKE) api-wait || { kill $$api_pid 2>/dev/null; exit 1; }; \
|
||||||
GUI_ENV_FILE="$(GUI_ENV_FILE)" $(MAKE) -C $(GUI) run; \
|
GUI_ENV_FILE="$(GUI_ENV_FILE)" $(MAKE) -C $(GUI) run; \
|
||||||
@@ -66,7 +78,7 @@ build-web: ## WebAssembly build -> build/web (gui/, needs emscripten)
|
|||||||
@$(MAKE) -C $(GUI) build-web
|
@$(MAKE) -C $(GUI) build-web
|
||||||
|
|
||||||
run-web: ## Start WASM build + API server for web dev (waits for API health)
|
run-web: ## Start WASM build + API server for web dev (waits for API health)
|
||||||
@API_ENV_FILE="$(API_ENV_FILE)" API_DESI_DATA="$(API_DESI_DATA)" API_BIND_ADDR="$(API_BIND_ADDR)" $(MAKE) -C $(API) run & api_pid=$$!; \
|
@API_ENV_FILE="$(API_ENV_FILE)" API_DESI_DATA="$(API_DESI_DATA)" $(if $(API_BIND_EXPLICIT),API_BIND_ADDR="$(API_BIND_ADDR)",) $(MAKE) -C $(API) run & api_pid=$$!; \
|
||||||
trap 'kill $$api_pid 2>/dev/null' INT TERM EXIT; \
|
trap 'kill $$api_pid 2>/dev/null' INT TERM EXIT; \
|
||||||
$(MAKE) api-wait || { kill $$api_pid 2>/dev/null; exit 1; }; \
|
$(MAKE) api-wait || { kill $$api_pid 2>/dev/null; exit 1; }; \
|
||||||
GUI_ENV_FILE="$(GUI_ENV_FILE)" $(MAKE) -C $(GUI) build-web; \
|
GUI_ENV_FILE="$(GUI_ENV_FILE)" $(MAKE) -C $(GUI) build-web; \
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
|
import "core:log"
|
||||||
import "core:net"
|
import "core:net"
|
||||||
import "core:strconv"
|
import "core:strconv"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
import "core:time"
|
import "core:time"
|
||||||
|
|
||||||
DEFAULT_HTTP_PORT :: 80
|
DEFAULT_HTTP_PORT :: 80
|
||||||
|
DEFAULT_HTTPS_PORT :: 443
|
||||||
MAX_REDIRECTS :: 5
|
MAX_REDIRECTS :: 5
|
||||||
HTTP_TIMEOUT :: 5 * time.Second
|
HTTP_TIMEOUT :: 5 * time.Second
|
||||||
|
|
||||||
@@ -33,27 +35,31 @@ Error_Code :: enum int {
|
|||||||
Too_Many_Redirects,
|
Too_Many_Redirects,
|
||||||
}
|
}
|
||||||
|
|
||||||
error_make :: proc(code: Error_Code) -> ^Error {
|
error_make :: proc(code: Error_Code, msg: string = "") -> ^Error {
|
||||||
err := new(Error)
|
err := new(Error)
|
||||||
err.code = int(code)
|
err.code = int(code)
|
||||||
|
err.message = msg
|
||||||
|
|
||||||
switch code {
|
if msg == "" {
|
||||||
case .Invalid_Url:
|
switch code {
|
||||||
err.message = "invalid url"
|
case .Invalid_Url:
|
||||||
case .Dial_Failed:
|
err.message = "invalid url"
|
||||||
err.message = "could not connect to host"
|
case .Dial_Failed:
|
||||||
case .Send_Failed:
|
err.message = "could not connect to host"
|
||||||
err.message = "could not send request"
|
case .Send_Failed:
|
||||||
case .Invalid_Response:
|
err.message = "could not send request"
|
||||||
err.message = "could not parse response"
|
case .Invalid_Response:
|
||||||
case .Invalid_Redirect:
|
err.message = "could not parse response"
|
||||||
err.message = "invalid redirect"
|
case .Invalid_Redirect:
|
||||||
case .Invalid_Chunked:
|
err.message = "invalid redirect"
|
||||||
err.message = "invalid chunked response"
|
case .Invalid_Chunked:
|
||||||
case .Too_Many_Redirects:
|
err.message = "invalid chunked response"
|
||||||
err.message = "too many redirects"
|
case .Too_Many_Redirects:
|
||||||
|
err.message = "too many redirects"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,12 +89,12 @@ http_get :: proc(url: string) -> (resp: Response, err: ^Error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
perform_request :: proc(url: string) -> (next_url: string, resp: Response, err: ^Error) {
|
perform_request :: proc(url: string) -> (next_url: string, resp: Response, err: ^Error) {
|
||||||
host, port, path, parse_ok := parse_http_url(url)
|
_, host, port, path, parse_ok := parse_http_url(url)
|
||||||
if !parse_ok {
|
if !parse_ok {
|
||||||
return "", {}, error_make(.Invalid_Url)
|
return "", {}, error_make(.Invalid_Url)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, dial_err := net.dial_tcp_from_hostname_with_port_override(host, port)
|
conn, dial_err := net.dial_tcp_from_host_or_endpoint(net.Host{host, port})
|
||||||
if dial_err != nil {
|
if dial_err != nil {
|
||||||
return "", {}, error_make(.Dial_Failed)
|
return "", {}, error_make(.Dial_Failed)
|
||||||
}
|
}
|
||||||
@@ -214,12 +220,21 @@ parse_status_code :: proc(status_line: string) -> int {
|
|||||||
return code if ok else 0
|
return code if ok else 0
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_http_url :: proc(url: string) -> (host: string, port: int, path: string, ok: bool) {
|
parse_http_url :: proc(
|
||||||
if strings.has_prefix(url, "https://") || !strings.has_prefix(url, "http://") {
|
url: string,
|
||||||
return "", 0, "", false
|
) -> (
|
||||||
|
scheme: string,
|
||||||
|
host: string,
|
||||||
|
port: int,
|
||||||
|
path: string,
|
||||||
|
ok: bool,
|
||||||
|
) {
|
||||||
|
if !strings.has_prefix(url, "https://") && !strings.has_prefix(url, "http://") {
|
||||||
|
return "", "", 0, "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
rest := url[len("http://"):]
|
https := strings.has_prefix(url, "https://")
|
||||||
|
rest := url[https ? len("https://") : len("http://"):]
|
||||||
host_and_port := rest
|
host_and_port := rest
|
||||||
if slash := strings.index_byte(rest, '/'); slash >= 0 {
|
if slash := strings.index_byte(rest, '/'); slash >= 0 {
|
||||||
host_and_port = rest[:slash]
|
host_and_port = rest[:slash]
|
||||||
@@ -229,20 +244,20 @@ parse_http_url :: proc(url: string) -> (host: string, port: int, path: string, o
|
|||||||
}
|
}
|
||||||
|
|
||||||
host = host_and_port
|
host = host_and_port
|
||||||
port = DEFAULT_HTTP_PORT
|
port = https ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT
|
||||||
if colon := strings.last_index_byte(host_and_port, ':'); colon >= 0 {
|
if colon := strings.last_index_byte(host_and_port, ':'); colon >= 0 {
|
||||||
parsed, p_ok := strconv.parse_int(host_and_port[colon + 1:])
|
parsed, p_ok := strconv.parse_int(host_and_port[colon + 1:])
|
||||||
if !p_ok {
|
if !p_ok {
|
||||||
return "", 0, "", false
|
return "", "", 0, "", false
|
||||||
}
|
}
|
||||||
host = host_and_port[:colon]
|
host = host_and_port[:colon]
|
||||||
port = parsed
|
port = parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
if host == "" {
|
if host == "" {
|
||||||
return "", 0, "", false
|
return "", "", 0, "", false
|
||||||
}
|
}
|
||||||
return host, port, path, true
|
return https ? "https" : "http", host, port, path, true
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve_redirect :: proc(base_url, location: string) -> string {
|
resolve_redirect :: proc(base_url, location: string) -> string {
|
||||||
@@ -309,3 +324,4 @@ fold_eq :: proc(a, b: string) -> bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import "core:thread"
|
|||||||
import http "../src"
|
import http "../src"
|
||||||
|
|
||||||
SERVER_BODY :: "hello world"
|
SERVER_BODY :: "hello world"
|
||||||
SERVER_RESPONSE := "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n" +
|
SERVER_RESPONSE :=
|
||||||
|
"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n" +
|
||||||
"Content-Length: 11\r\n" +
|
"Content-Length: 11\r\n" +
|
||||||
"Connection: close\r\n\r\n" +
|
"Connection: close\r\n\r\n" +
|
||||||
SERVER_BODY
|
SERVER_BODY
|
||||||
@@ -57,7 +58,9 @@ test_http_get_request :: proc(t: ^testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
args: Serve_Args = {listener = listener}
|
args: Serve_Args = {
|
||||||
|
listener = listener,
|
||||||
|
}
|
||||||
server_thread := thread.create_and_start_with_data(&args, serve_proc)
|
server_thread := thread.create_and_start_with_data(&args, serve_proc)
|
||||||
defer thread.destroy(server_thread)
|
defer thread.destroy(server_thread)
|
||||||
|
|
||||||
@@ -66,7 +69,7 @@ test_http_get_request :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
log.infof("requesting %s", url)
|
log.infof("requesting %s", url)
|
||||||
resp, req_err := http.http_get(url)
|
resp, req_err := http.http_get(url)
|
||||||
if !testing.expect(t, req_err == nil, "http_get returned an error") {
|
if !testing.expectf(t, req_err == nil, "http_get returned an error: %w", req_err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer delete(resp.body)
|
defer delete(resp.body)
|
||||||
@@ -74,3 +77,73 @@ test_http_get_request :: proc(t: ^testing.T) {
|
|||||||
testing.expectf(t, resp.status == 200, "expected status 200, got %d", resp.status)
|
testing.expectf(t, resp.status == 200, "expected status 200, got %d", resp.status)
|
||||||
testing.expectf(t, resp.body == SERVER_BODY, "expected %q, got %q", SERVER_BODY, resp.body)
|
testing.expectf(t, resp.body == SERVER_BODY, "expected %q, got %q", SERVER_BODY, resp.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_http_parse_url :: proc(t: ^testing.T) {
|
||||||
|
testURL :: struct {
|
||||||
|
url: string,
|
||||||
|
expectedScheme: string,
|
||||||
|
expectedOK: bool,
|
||||||
|
expectedHost: string,
|
||||||
|
expectedPort: int,
|
||||||
|
expectedPath: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
testURLs := []testURL {
|
||||||
|
{"http://localhost:8080", "http", true, "localhost", 8080, "/"},
|
||||||
|
{"http://localhost", "http", true, "localhost", 80, "/"},
|
||||||
|
{"https://localhost:443", "https", true, "localhost", 443, "/"},
|
||||||
|
{"https://localhost", "https", true, "localhost", 443, "/"},
|
||||||
|
{"http://test:6969", "http", true, "test", 6969, "/"},
|
||||||
|
{
|
||||||
|
"http://localhost:3000/api/v1/catalogs",
|
||||||
|
"http",
|
||||||
|
true,
|
||||||
|
"localhost",
|
||||||
|
3000,
|
||||||
|
"/api/v1/catalogs",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for tu in testURLs {
|
||||||
|
scheme, host, port, path, ok := http.parse_http_url(tu.url)
|
||||||
|
|
||||||
|
testing.expectf(
|
||||||
|
t,
|
||||||
|
ok == tu.expectedOK,
|
||||||
|
"expected ok from parsing to be %v but got %v",
|
||||||
|
tu.expectedOK,
|
||||||
|
ok,
|
||||||
|
)
|
||||||
|
testing.expectf(
|
||||||
|
t,
|
||||||
|
host == tu.expectedHost,
|
||||||
|
"expected host url to be '%s' but got '%s'",
|
||||||
|
tu.expectedHost,
|
||||||
|
host,
|
||||||
|
)
|
||||||
|
testing.expectf(
|
||||||
|
t,
|
||||||
|
scheme == tu.expectedScheme,
|
||||||
|
"expected scheme to be '%s' but got '%s'",
|
||||||
|
tu.expectedScheme,
|
||||||
|
scheme,
|
||||||
|
)
|
||||||
|
testing.expectf(
|
||||||
|
t,
|
||||||
|
port == tu.expectedPort,
|
||||||
|
"expected port to be %d but got %d",
|
||||||
|
tu.expectedPort,
|
||||||
|
port,
|
||||||
|
)
|
||||||
|
testing.expectf(
|
||||||
|
t,
|
||||||
|
path == tu.expectedPath,
|
||||||
|
"expected path to be '%s' but got '%s'",
|
||||||
|
tu.expectedPath,
|
||||||
|
path,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-10
@@ -83,7 +83,10 @@ request_json_array :: proc(
|
|||||||
defer delete(resp.body)
|
defer delete(resp.body)
|
||||||
|
|
||||||
if resp.status != 200 {
|
if resp.status != 200 {
|
||||||
return nil, json.Null(nil), api_error(resp.status, strings.clone(strings.trim_space(resp.body))), false
|
return nil,
|
||||||
|
json.Null(nil),
|
||||||
|
api_error(resp.status, strings.clone(strings.trim_space(resp.body))),
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debugf("got %s => %s", url, resp.body)
|
log.debugf("got %s => %s", url, resp.body)
|
||||||
@@ -105,6 +108,8 @@ get_catalogs :: proc(base_url: string) -> ([dynamic]Catalog, ^APIError) {
|
|||||||
url := endpoint(base_url, "/api/v1/catalogs")
|
url := endpoint(base_url, "/api/v1/catalogs")
|
||||||
defer delete(url)
|
defer delete(url)
|
||||||
|
|
||||||
|
log.infof("getting catalogs from url: %s", url)
|
||||||
|
|
||||||
arr, val, err, ok := request_json_array(url)
|
arr, val, err, ok := request_json_array(url)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -157,7 +162,7 @@ catalog_from_json :: proc(v: json.Value) -> (c: Catalog, ok: bool) {
|
|||||||
if !is_obj {
|
if !is_obj {
|
||||||
return {}, false
|
return {}, false
|
||||||
}
|
}
|
||||||
c = Catalog{
|
c = Catalog {
|
||||||
name = json_string(obj, "name"),
|
name = json_string(obj, "name"),
|
||||||
release = json_string(obj, "release"),
|
release = json_string(obj, "release"),
|
||||||
}
|
}
|
||||||
@@ -177,14 +182,15 @@ catalog_object_from_json :: proc(v: json.Value) -> (o: CatalogObject, ok: bool)
|
|||||||
if !is_obj {
|
if !is_obj {
|
||||||
return {}, false
|
return {}, false
|
||||||
}
|
}
|
||||||
return CatalogObject{
|
return CatalogObject {
|
||||||
id = json_string(obj, "id"),
|
id = json_string(obj, "id"),
|
||||||
catalog = json_string(obj, "catalog"),
|
catalog = json_string(obj, "catalog"),
|
||||||
object_type = json_string(obj, "object_type"),
|
object_type = json_string(obj, "object_type"),
|
||||||
ra = json_f64(obj, "ra"),
|
ra = json_f64(obj, "ra"),
|
||||||
dec = json_f64(obj, "dec"),
|
dec = json_f64(obj, "dec"),
|
||||||
redshift = json_f64(obj, "redshift"),
|
redshift = json_f64(obj, "redshift"),
|
||||||
}, true
|
},
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
json_string :: proc(obj: json.Object, key: string) -> string {
|
json_string :: proc(obj: json.Object, key: string) -> string {
|
||||||
@@ -219,3 +225,4 @@ json_u64 :: proc(obj: json.Object, key: string) -> (u64, bool) {
|
|||||||
}
|
}
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-6
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import "base:runtime"
|
import "base:runtime"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
|
import "core:log"
|
||||||
import "core:math"
|
import "core:math"
|
||||||
import "core:math/rand"
|
import "core:math/rand"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
@@ -51,6 +52,8 @@ main :: proc() {
|
|||||||
c: ^Config
|
c: ^Config
|
||||||
err: ^Error
|
err: ^Error
|
||||||
|
|
||||||
|
context.logger = log.create_console_logger()
|
||||||
|
defer log.destroy_console_logger(context.logger)
|
||||||
s := os.get_env("GUI_ENV_FILE", context.temp_allocator)
|
s := os.get_env("GUI_ENV_FILE", context.temp_allocator)
|
||||||
|
|
||||||
if c, err = get_config(&s); err != nil {
|
if c, err = get_config(&s); err != nil {
|
||||||
@@ -82,7 +85,7 @@ main :: proc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
make_app :: proc(c: ^Config) -> App {
|
make_app :: proc(c: ^Config) -> App {
|
||||||
a := App{
|
a := App {
|
||||||
screen = .Start,
|
screen = .Start,
|
||||||
selected_catalog = -1,
|
selected_catalog = -1,
|
||||||
}
|
}
|
||||||
@@ -90,7 +93,10 @@ make_app :: proc(c: ^Config) -> App {
|
|||||||
a.region = strings.builder_make()
|
a.region = strings.builder_make()
|
||||||
a.status = strings.builder_make()
|
a.status = strings.builder_make()
|
||||||
strings.write_string(&a.api_url, c.api_url)
|
strings.write_string(&a.api_url, c.api_url)
|
||||||
strings.write_string(&a.status, "Set the API URL, press Refresh, pick a catalog, then Explore.")
|
strings.write_string(
|
||||||
|
&a.status,
|
||||||
|
"Set the API URL, press Refresh, pick a catalog, then Explore.",
|
||||||
|
)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,10 +112,10 @@ destroy_app :: proc(a: ^App) {
|
|||||||
|
|
||||||
make_camera :: proc() -> rl.Camera3D {
|
make_camera :: proc() -> rl.Camera3D {
|
||||||
return {
|
return {
|
||||||
position = {0, 220, 220},
|
position = {0, 220, 220},
|
||||||
target = {0, 0, 0},
|
target = {0, 0, 0},
|
||||||
up = {0, 1, 0},
|
up = {0, 1, 0},
|
||||||
fovy = 60,
|
fovy = 60,
|
||||||
projection = .PERSPECTIVE,
|
projection = .PERSPECTIVE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,3 +216,4 @@ draw :: proc() {
|
|||||||
{120, 120, 140, 255},
|
{120, 120, 140, 255},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user