vulkan fixes (correctness) and something about the valve BSP / dark MIS integration
This commit is contained in:
parent
5ba9983594
commit
df4e9dcb36
343
Makefile
343
Makefile
@ -1,171 +1,172 @@
|
||||
# Defaults
|
||||
DEFAULTS_DIR := ./makefiles/defaults
|
||||
_mkdir := $(shell mkdir -p $(DEFAULTS_DIR))
|
||||
|
||||
ifeq ($(origin ARCH),undefined)
|
||||
ARCH := $(shell cat "$(DEFAULTS_DIR)/arch" 2>/dev/null)
|
||||
endif
|
||||
ifeq ($(strip $(ARCH)),)
|
||||
ifeq ($(OS),Windows_NT)
|
||||
ARCH := win64
|
||||
else
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
ARCH := linux
|
||||
else
|
||||
$(warning Unknown host '$(UNAME_S)', defaulting ARCH to linux)
|
||||
ARCH := linux
|
||||
endif
|
||||
endif
|
||||
_write_arch := $(shell echo -n "$(ARCH)" > "$(DEFAULTS_DIR)/arch")
|
||||
endif
|
||||
|
||||
ifeq ($(origin COMPILER),undefined)
|
||||
COMPILER := $(shell cat "$(DEFAULTS_DIR)/cc" 2>/dev/null)
|
||||
endif
|
||||
ifeq ($(strip $(COMPILER)),)
|
||||
ifneq ($(shell command -v gcc 2>/dev/null),)
|
||||
COMPILER := gcc
|
||||
else ifneq ($(shell command -v clang 2>/dev/null),)
|
||||
COMPILER := clang
|
||||
else
|
||||
$(warning No gcc or clang found, defaulting COMPILER to gcc)
|
||||
COMPILER := gcc
|
||||
endif
|
||||
_write_cc := $(shell echo -n "$(COMPILER)" > "$(DEFAULTS_DIR)/cc")
|
||||
endif
|
||||
|
||||
# to-do: deduce via existence of Vulkan/OpenGL headers
|
||||
ifeq ($(origin RENDERER),undefined)
|
||||
RENDERER := $(shell cat "$(DEFAULTS_DIR)/renderer" 2>/dev/null)
|
||||
endif
|
||||
ifeq ($(strip $(RENDERER)),)
|
||||
RENDERER := vulkan
|
||||
_write_rend := $(shell echo -n "$(RENDERER)" > "$(DEFAULTS_DIR)/renderer")
|
||||
endif
|
||||
|
||||
TARGET_NAME = program
|
||||
TARGET_EXTENSION = .exe
|
||||
DLIB_EXTENSION = .dll
|
||||
SLIB_EXTENSION = .a
|
||||
PREFIX = $(ARCH).$(COMPILER).$(RENDERER)
|
||||
|
||||
# Basic Paths
|
||||
CXX := $(CDIR)$(CXX)
|
||||
BIN_DIR += ./bin
|
||||
|
||||
ENGINE_SRC_DIR += ./engine/src
|
||||
ENGINE_INC_DIR += ./engine/inc
|
||||
ENGINE_LIB_DIR += ./engine/lib
|
||||
DEP_SRC_DIR += ./dep/src
|
||||
EXT_SRC_DIR += ./ext
|
||||
CLIENT_SRC_DIR += ./client
|
||||
|
||||
# Base Flags
|
||||
OPTIMIZATIONS = -O3 -fstrict-aliasing -DUF_NO_EXCEPTIONS
|
||||
WARNINGS = -Wall -Wno-unknown-pragmas -Wno-unused-function -Wno-unused-variable -Wno-switch -Wno-reorder -Wno-sign-compare -Wno-unused-but-set-variable -Wno-ignored-attributes -Wno-narrowing -Wno-misleading-indentation
|
||||
FLAGS += -std=c++2b $(OPTIMIZATIONS) $(WARNINGS) -fdiagnostics-color=always
|
||||
|
||||
# Base Library Definitions
|
||||
LIB_NAME += uf
|
||||
EXT_LIB_NAME += ext
|
||||
PREFIX_PATH = $(ARCH)/$(COMPILER)/$(RENDERER)
|
||||
INC_DIR += $(ENGINE_INC_DIR)
|
||||
LIB_DIR += $(ENGINE_LIB_DIR)
|
||||
|
||||
INCS += -I$(ENGINE_INC_DIR) -I./dep/include/
|
||||
LIBS += -L$(ENGINE_LIB_DIR) -L$(LIB_DIR)/$(PREFIX_PATH)/ -L$(LIB_DIR)/$(ARCH)/$(COMPILER)/ -L$(LIB_DIR)/$(ARCH)/
|
||||
LINKS += $(UF_LIBS) $(EXT_LIBS) $(DEPS)
|
||||
|
||||
# DLL
|
||||
SRCS_DLL := $(shell find $(ENGINE_SRC_DIR) -name "*.cpp") $(shell find $(DEP_SRC_DIR) -name "*.cpp")
|
||||
SRCS_DLL_C := $(shell find $(ENGINE_SRC_DIR) -name "*.c") $(shell find $(DEP_SRC_DIR) -name "*.c")
|
||||
OBJS_DLL += $(patsubst %.cpp,%.$(PREFIX).o,$(SRCS_DLL)) $(patsubst %.c,%.$(PREFIX).o,$(SRCS_DLL_C))
|
||||
BASE_DLL += lib$(LIB_NAME)
|
||||
|
||||
IM_DLL += $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_DLL)$(DLIB_EXTENSION)
|
||||
EX_DLL += $(BIN_DIR)/exe/lib/$(PREFIX_PATH)/$(BASE_DLL)$(DLIB_EXTENSION)
|
||||
|
||||
EXT_IM_DLL += $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_EXT_DLL)$(DLIB_EXTENSION)
|
||||
EXT_EX_DLL += $(BIN_DIR)/exe/lib/$(PREFIX_PATH)/$(BASE_EXT_DLL)$(DLIB_EXTENSION)
|
||||
|
||||
SRCS_EXT_DLL := $(shell find $(EXT_SRC_DIR) -name "*.cpp")
|
||||
OBJS_EXT_DLL += $(patsubst %.cpp,%.$(PREFIX).o,$(SRCS_EXT_DLL))
|
||||
BASE_EXT_DLL += lib$(EXT_LIB_NAME)
|
||||
|
||||
EXT_DEPS += -l$(LIB_NAME) $(DEPS)
|
||||
EXT_INC_DIR += $(INC_DIR)
|
||||
EXT_INCS += $(INCS)
|
||||
EXT_LIBS += $(LIBS)
|
||||
EXT_LINKS += $(UF_LIBS) $(EXT_LIBS) $(EXT_DEPS)
|
||||
|
||||
# Executable
|
||||
SRCS := $(shell find $(CLIENT_SRC_DIR) -name "*.cpp")
|
||||
OBJS += $(patsubst %.cpp,%.$(PREFIX).o,$(SRCS))
|
||||
TARGET += $(BIN_DIR)/exe/$(TARGET_NAME).$(PREFIX)$(TARGET_EXTENSION)
|
||||
|
||||
# Shaders
|
||||
SRCS_SHADERS := $(shell find bin/data/shaders/ -name "*.glsl")
|
||||
TARGET_SHADERS += $(patsubst %.glsl,%.spv,$(SRCS_SHADERS))
|
||||
|
||||
.DEFAULT_GOAL := $(PREFIX)
|
||||
|
||||
.PHONY: $(PREFIX) clean run run-debug clean-shaders backup
|
||||
.FORCE:
|
||||
|
||||
include makefiles/platforms/$(ARCH).$(COMPILER).mk
|
||||
|
||||
ifneq (,$(findstring win64,$(ARCH)))
|
||||
include makefiles/platforms/win64.mk
|
||||
else ifneq (,$(findstring linux,$(ARCH)))
|
||||
include makefiles/platforms/linux.mk
|
||||
else ifneq (,$(findstring dreamcast,$(ARCH)))
|
||||
include makefiles/platforms/dreamcast.mk
|
||||
endif
|
||||
|
||||
include makefiles/dependencies.mk
|
||||
|
||||
# Build Rules
|
||||
$(PREFIX): $(EX_DLL) $(EXT_EX_DLL) $(TARGET) $(TARGET_SHADERS)
|
||||
|
||||
%.$(PREFIX).o: %.cpp
|
||||
$(CXX) $(FLAGS) $(INCS) -c $< -o $@
|
||||
|
||||
%.$(PREFIX).o: %.c
|
||||
$(CC) $(FLAGS) $(INCS) -c $< -o $@
|
||||
|
||||
ifneq ($(ARCH),dreamcast)
|
||||
$(TARGET): $(OBJS)
|
||||
$(CXX) $(FLAGS) $(OBJS) $(LIBS) $(INCS) $(LINKS) -l$(LIB_NAME) -l$(EXT_LIB_NAME) -o $(TARGET)
|
||||
endif
|
||||
|
||||
%.spv: %.glsl
|
||||
$(GLSLC) --target-env=vulkan1.2 -o $@ $<
|
||||
@-$(SPV_LINTER) $@
|
||||
@-$(SPV_OPTIMIZER) --preserve-bindings --preserve-spec-constants -O $@ -o $@
|
||||
|
||||
shaders: $(TARGET_SHADERS)
|
||||
|
||||
clean:
|
||||
@-rm $(EX_DLL)
|
||||
@-rm $(EXT_EX_DLL)
|
||||
@-rm $(TARGET)
|
||||
|
||||
@-rm -f $(OBJS_DLL)
|
||||
@-rm -f $(OBJS_EXT_DLL)
|
||||
@-rm -f $(OBJS)
|
||||
|
||||
clean-shaders:
|
||||
@-rm -f $(TARGET_SHADERS)
|
||||
|
||||
run:
|
||||
@echo -n $(ARCH) > "./bin/exe/default/arch"
|
||||
@echo -n $(COMPILER) > "./bin/exe/default/cc"
|
||||
@echo -n $(RENDERER) > "./bin/exe/default/renderer"
|
||||
./program.sh
|
||||
|
||||
run-debug:
|
||||
@echo -n $(ARCH) > "./bin/exe/default/arch"
|
||||
@echo -n $(COMPILER) > "./bin/exe/default/cc"
|
||||
@echo -n $(RENDERER) > "./bin/exe/default/renderer"
|
||||
./debug.sh
|
||||
# Defaults
|
||||
DEFAULTS_DIR := ./makefiles/defaults
|
||||
_mkdir := $(shell mkdir -p $(DEFAULTS_DIR))
|
||||
|
||||
ifeq ($(origin ARCH),undefined)
|
||||
ARCH := $(shell cat "$(DEFAULTS_DIR)/arch" 2>/dev/null)
|
||||
endif
|
||||
ifeq ($(strip $(ARCH)),)
|
||||
ifeq ($(OS),Windows_NT)
|
||||
ARCH := win64
|
||||
else
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
ARCH := linux
|
||||
else
|
||||
$(warning Unknown host '$(UNAME_S)', defaulting ARCH to linux)
|
||||
ARCH := linux
|
||||
endif
|
||||
endif
|
||||
_write_arch := $(shell echo -n "$(ARCH)" > "$(DEFAULTS_DIR)/arch")
|
||||
endif
|
||||
|
||||
ifeq ($(origin COMPILER),undefined)
|
||||
COMPILER := $(shell cat "$(DEFAULTS_DIR)/cc" 2>/dev/null)
|
||||
endif
|
||||
ifeq ($(strip $(COMPILER)),)
|
||||
ifneq ($(shell command -v gcc 2>/dev/null),)
|
||||
COMPILER := gcc
|
||||
else ifneq ($(shell command -v clang 2>/dev/null),)
|
||||
COMPILER := clang
|
||||
else
|
||||
$(warning No gcc or clang found, defaulting COMPILER to gcc)
|
||||
COMPILER := gcc
|
||||
endif
|
||||
_write_cc := $(shell echo -n "$(COMPILER)" > "$(DEFAULTS_DIR)/cc")
|
||||
endif
|
||||
|
||||
# to-do: deduce via existence of Vulkan/OpenGL headers
|
||||
ifeq ($(origin RENDERER),undefined)
|
||||
RENDERER := $(shell cat "$(DEFAULTS_DIR)/renderer" 2>/dev/null)
|
||||
endif
|
||||
ifeq ($(strip $(RENDERER)),)
|
||||
RENDERER := vulkan
|
||||
_write_rend := $(shell echo -n "$(RENDERER)" > "$(DEFAULTS_DIR)/renderer")
|
||||
endif
|
||||
|
||||
TARGET_NAME = program
|
||||
TARGET_EXTENSION = .exe
|
||||
DLIB_EXTENSION = .dll
|
||||
SLIB_EXTENSION = .a
|
||||
PREFIX = $(ARCH).$(COMPILER).$(RENDERER)
|
||||
|
||||
# Basic Paths
|
||||
CXX := $(CDIR)$(CXX)
|
||||
BIN_DIR += ./bin
|
||||
|
||||
ENGINE_SRC_DIR += ./engine/src
|
||||
ENGINE_INC_DIR += ./engine/inc
|
||||
ENGINE_LIB_DIR += ./engine/lib
|
||||
DEP_SRC_DIR += ./dep/src
|
||||
EXT_SRC_DIR += ./ext
|
||||
CLIENT_SRC_DIR += ./client
|
||||
|
||||
# Base Flags
|
||||
OPTIMIZATIONS = -O3 -fstrict-aliasing -DUF_NO_EXCEPTIONS
|
||||
WARNINGS = -Wall -Wno-unknown-pragmas -Wno-unused-function -Wno-unused-variable -Wno-switch -Wno-reorder -Wno-sign-compare -Wno-unused-but-set-variable -Wno-ignored-attributes -Wno-narrowing -Wno-misleading-indentation
|
||||
FLAGS += -std=c++2b $(OPTIMIZATIONS) $(WARNINGS) -fdiagnostics-color=always
|
||||
|
||||
# Base Library Definitions
|
||||
LIB_NAME += uf
|
||||
EXT_LIB_NAME += ext
|
||||
PREFIX_PATH = $(ARCH)/$(COMPILER)/$(RENDERER)
|
||||
INC_DIR += $(ENGINE_INC_DIR)
|
||||
LIB_DIR += $(ENGINE_LIB_DIR)
|
||||
|
||||
INCS += -I$(ENGINE_INC_DIR) -I./dep/include/
|
||||
LIBS += -L$(ENGINE_LIB_DIR) -L$(LIB_DIR)/$(PREFIX_PATH)/ -L$(LIB_DIR)/$(ARCH)/$(COMPILER)/ -L$(LIB_DIR)/$(ARCH)/
|
||||
LINKS += $(UF_LIBS) $(EXT_LIBS) $(DEPS)
|
||||
|
||||
# DLL
|
||||
SRCS_DLL := $(shell find $(ENGINE_SRC_DIR) -name "*.cpp") $(shell find $(DEP_SRC_DIR) -name "*.cpp")
|
||||
SRCS_DLL_C := $(shell find $(ENGINE_SRC_DIR) -name "*.c") $(shell find $(DEP_SRC_DIR) -name "*.c")
|
||||
OBJS_DLL += $(patsubst %.cpp,%.$(PREFIX).o,$(SRCS_DLL)) $(patsubst %.c,%.$(PREFIX).o,$(SRCS_DLL_C))
|
||||
BASE_DLL += lib$(LIB_NAME)
|
||||
|
||||
IM_DLL += $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_DLL)$(DLIB_EXTENSION)
|
||||
EX_DLL += $(BIN_DIR)/exe/lib/$(PREFIX_PATH)/$(BASE_DLL)$(DLIB_EXTENSION)
|
||||
|
||||
EXT_IM_DLL += $(ENGINE_LIB_DIR)/$(PREFIX_PATH)/$(BASE_EXT_DLL)$(DLIB_EXTENSION)
|
||||
EXT_EX_DLL += $(BIN_DIR)/exe/lib/$(PREFIX_PATH)/$(BASE_EXT_DLL)$(DLIB_EXTENSION)
|
||||
|
||||
SRCS_EXT_DLL := $(shell find $(EXT_SRC_DIR) -name "*.cpp")
|
||||
OBJS_EXT_DLL += $(patsubst %.cpp,%.$(PREFIX).o,$(SRCS_EXT_DLL))
|
||||
BASE_EXT_DLL += lib$(EXT_LIB_NAME)
|
||||
|
||||
EXT_DEPS += -l$(LIB_NAME) $(DEPS)
|
||||
EXT_INC_DIR += $(INC_DIR)
|
||||
EXT_INCS += $(INCS)
|
||||
EXT_LIBS += $(LIBS)
|
||||
EXT_LINKS += $(UF_LIBS) $(EXT_LIBS) $(EXT_DEPS)
|
||||
|
||||
# Executable
|
||||
SRCS := $(shell find $(CLIENT_SRC_DIR) -name "*.cpp")
|
||||
OBJS += $(patsubst %.cpp,%.$(PREFIX).o,$(SRCS))
|
||||
TARGET += $(BIN_DIR)/exe/$(TARGET_NAME).$(PREFIX)$(TARGET_EXTENSION)
|
||||
|
||||
# Shaders
|
||||
SRCS_SHADERS := $(shell find bin/data/shaders/ -name "*.glsl")
|
||||
TARGET_SHADERS += $(patsubst %.glsl,%.spv,$(SRCS_SHADERS))
|
||||
|
||||
.DEFAULT_GOAL := $(PREFIX)
|
||||
|
||||
.PHONY: $(PREFIX) clean run run-debug clean-shaders backup
|
||||
.FORCE:
|
||||
|
||||
include makefiles/platforms/$(ARCH).$(COMPILER).mk
|
||||
|
||||
ifneq (,$(findstring win64,$(ARCH)))
|
||||
include makefiles/platforms/win64.mk
|
||||
else ifneq (,$(findstring linux,$(ARCH)))
|
||||
include makefiles/platforms/linux.mk
|
||||
else ifneq (,$(findstring dreamcast,$(ARCH)))
|
||||
include makefiles/platforms/dreamcast.mk
|
||||
endif
|
||||
|
||||
include makefiles/dependencies.mk
|
||||
|
||||
# Build Rules
|
||||
$(PREFIX): $(EX_DLL) $(EXT_EX_DLL) $(TARGET) $(TARGET_SHADERS)
|
||||
|
||||
%.$(PREFIX).o: %.cpp
|
||||
$(CXX) $(FLAGS) $(INCS) -c $< -o $@
|
||||
|
||||
%.$(PREFIX).o: %.c
|
||||
$(CC) $(FLAGS) $(INCS) -c $< -o $@
|
||||
|
||||
ifneq ($(ARCH),dreamcast)
|
||||
$(TARGET): $(OBJS)
|
||||
$(CXX) $(FLAGS) $(OBJS) $(LIBS) $(INCS) $(LINKS) -l$(LIB_NAME) -l$(EXT_LIB_NAME) -o $(TARGET)
|
||||
endif
|
||||
|
||||
%.spv: %.glsl
|
||||
$(GLSLC) --target-env=vulkan1.2 -o $@ $<
|
||||
@-$(SPV_LINTER) $@
|
||||
@-$(SPV_OPTIMIZER) --preserve-bindings --preserve-spec-constants -O $@ -o $@
|
||||
|
||||
shaders: $(TARGET_SHADERS)
|
||||
|
||||
clean:
|
||||
@-rm $(EX_DLL)
|
||||
@-rm $(EXT_EX_DLL)
|
||||
@-rm $(TARGET)
|
||||
|
||||
@-rm -f $(OBJS_DLL)
|
||||
@-rm -f $(OBJS_EXT_DLL)
|
||||
@-rm -f $(OBJS)
|
||||
|
||||
clean-shaders:
|
||||
@-rm -f $(TARGET_SHADERS)
|
||||
|
||||
run:
|
||||
@echo -n $(ARCH) > "./bin/exe/default/arch"
|
||||
@echo -n $(COMPILER) > "./bin/exe/default/cc"
|
||||
@echo -n $(RENDERER) > "./bin/exe/default/renderer"
|
||||
./program.sh
|
||||
|
||||
run-debug:
|
||||
@echo -n $(ARCH) > "./bin/exe/default/arch"
|
||||
@echo -n $(COMPILER) > "./bin/exe/default/cc"
|
||||
@echo -n $(RENDERER) > "./bin/exe/default/renderer"
|
||||
./debug.sh
|
||||
|
||||
|
||||
@ -1,89 +1,509 @@
|
||||
_G.OSM = _G.OSM or {}
|
||||
|
||||
_G.OSM["TweqLockedButton"] = {
|
||||
onMessage = function(entity, payload, dMeta) end,
|
||||
|
||||
onFrob = function(entity, payload, dMeta)
|
||||
local eName = entity:name() or entity:uid()
|
||||
-- todo: deduce lock state
|
||||
local isLocked = true
|
||||
|
||||
print(entity, "is locked?", isLocked)
|
||||
|
||||
if isLocked then
|
||||
-- print(string.format("%s is locked! Emitting 'cardfail'.", eName))
|
||||
_G.DarkUtils.playSound(entity, "", "cardfail", { spatial = true, maxDistance = 15.0 })
|
||||
-- entity:callHook("ui:FlashMessage", { text = "Access Required: {}" })
|
||||
else
|
||||
-- unlock logic
|
||||
local cTags = (dMeta["class_tags"] or "") .. ", Event StateChange"
|
||||
_G.DarkUtils.playSound(entity, cTags, "", { spatial = true, maxDistance = 15.0 })
|
||||
|
||||
entity:callHook("link:Broadcast.%UID%", {
|
||||
message = "TurnOn", flavors = { "ControlDevice" }, caller = payload.user, callerDarkID = dMeta["id"]
|
||||
})
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
|
||||
_G.OSM["RelayTrap"] = function(entity, payload, dMeta)
|
||||
local msg = payload.message
|
||||
entity:callHook("link:Broadcast.%UID%", {
|
||||
message = msg,
|
||||
flavors = { "ControlDevice", "SwitchLink" },
|
||||
caller = entity:uid(),
|
||||
callerDarkID = payload.callerDarkID
|
||||
})
|
||||
end
|
||||
|
||||
_G.OSM["TrapQBFilter"] = _G.OSM["RelayTrap"]
|
||||
_G.OSM["TrapQBNegFilter"] = _G.OSM["RelayTrap"]
|
||||
_G.OSM["ElevatorButton"] = _G.OSM["RelayTrap"]
|
||||
|
||||
_G.OSM["RequireAllTrap"] = function(entity, payload, dMeta)
|
||||
local msg = payload.message
|
||||
local callerDarkID = payload.callerDarkID
|
||||
|
||||
_G.RAT_States = _G.RAT_States or {}
|
||||
local uid = entity:uid()
|
||||
_G.RAT_States[uid] = _G.RAT_States[uid] or { inputs = {}, wasOn = false }
|
||||
local state = _G.RAT_States[uid]
|
||||
|
||||
if callerDarkID then
|
||||
state.inputs[callerDarkID] = (msg == "TurnOn")
|
||||
end
|
||||
|
||||
local allOn = true
|
||||
local incoming = dMeta["incoming_connections"] or {}
|
||||
for i = 1, #incoming do
|
||||
local conn = incoming[i]
|
||||
if conn.flavor == "ControlDevice" or conn.flavor == "SwitchLink" then
|
||||
if not state.inputs[conn.source_id] then
|
||||
allOn = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if allOn and not state.wasOn then
|
||||
state.wasOn = true
|
||||
entity:callHook("link:Broadcast.%UID%", { message = "TurnOn", flavors = { "ControlDevice", "SwitchLink" }, callerDarkID = dMeta["id"], caller = uid })
|
||||
elseif not allOn and state.wasOn then
|
||||
state.wasOn = false
|
||||
entity:callHook("link:Broadcast.%UID%", { message = "TurnOff", flavors = { "ControlDevice", "SwitchLink" }, callerDarkID = dMeta["id"], caller = uid })
|
||||
end
|
||||
end
|
||||
|
||||
_G.OSM["BaseElevator"] = function(entity, payload, dMeta)
|
||||
local msg = payload.message
|
||||
local eName = entity:name() or entity:uid()
|
||||
|
||||
-- print(string.format("%s (Elevator) received command: %s", eName, msg))
|
||||
|
||||
if msg == "TurnOn" or msg == "TurnOff" then
|
||||
-- print(string.format("--> Commanding Lift '%s' to move!", eName))
|
||||
end
|
||||
end
|
||||
|
||||
_G.OSM["Elevator"] = _G.OSM["BaseElevator"]
|
||||
_G.OSM = _G.OSM or {}
|
||||
_G.DarkUtils = _G.DarkUtils or {}
|
||||
_G.DarkQB = _G.DarkQB or {}
|
||||
|
||||
_G.OSM_States = _G.OSM_States or {}
|
||||
local function getState( entity )
|
||||
local uid = entity:uid()
|
||||
_G.OSM_States[uid] = _G.OSM_States[uid] or {}
|
||||
return _G.OSM_States[uid]
|
||||
end
|
||||
|
||||
-- play a frob/activate sound for an object, falling back through tag queries
|
||||
local function playActivateSound( entity, dMeta )
|
||||
local tags = dMeta["class_tags"] or ""
|
||||
if tags ~= "" then tags = tags .. ", " end
|
||||
local played = _G.DarkUtils.playSound( entity, tags .. "Event Activate", nil, { spatial = true, maxDistance = 15.0 } )
|
||||
if not played then played = _G.DarkUtils.playSound( entity, tags .. "Event StateChange", nil, { spatial = true, maxDistance = 15.0 } ) end
|
||||
if not played then _G.DarkUtils.playSound( entity, dMeta["class_tags"] or "", nil, { spatial = true, maxDistance = 15.0 } ) end
|
||||
end
|
||||
|
||||
local function broadcast( entity, dMeta, message, caller, flavors )
|
||||
entity:callHook( "link:Broadcast.%UID%", {
|
||||
message = message,
|
||||
flavors = flavors or { "ControlDevice", "SwitchLink" },
|
||||
caller = caller or entity:uid(),
|
||||
callerDarkID = dMeta["id"]
|
||||
})
|
||||
end
|
||||
|
||||
-- resolve a dark object id to an entity
|
||||
local function getTarget( darkID )
|
||||
local targetUID = _G.DarkTargets and _G.DarkTargets[darkID]
|
||||
if not targetUID then return nil end
|
||||
local targetEnt = entities.get(targetUID)
|
||||
if targetEnt and targetEnt:uid() then return targetEnt end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- find the first connection matching a flavor substring, returns (connection, target entity)
|
||||
local function findConnection( dMeta, flavorMatch )
|
||||
local conns = dMeta["connections"] or {}
|
||||
for i = 1, #conns do
|
||||
local conn = conns[i]
|
||||
if string.find( conn.flavor or "", flavorMatch, 1, true ) then
|
||||
return conn, getTarget( conn.target_id )
|
||||
end
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
-- per-object state cleanup
|
||||
local ent = ent
|
||||
ent:addHook( "entity:Destroy.%UID%", function()
|
||||
if _G.OSM_States[ent:uid()] then _G.OSM_States[ent:uid()] = nil end
|
||||
end)
|
||||
|
||||
-- relays / logic
|
||||
_G.OSM["RelayTrap"] = function(entity, payload, dMeta)
|
||||
local msg = payload.message
|
||||
broadcast( entity, dMeta, msg, entity:uid(), { "ControlDevice", "SwitchLink" } )
|
||||
end
|
||||
|
||||
-- forwards any message it receives (including Toggle)
|
||||
_G.OSM["TrapRouter"] = _G.OSM["RelayTrap"]
|
||||
|
||||
-- relays the first message it receives, then goes inert
|
||||
_G.OSM["OnceRouter"] = function(entity, payload, dMeta)
|
||||
local st = getState( entity )
|
||||
if st.onceFired then return end
|
||||
st.onceFired = true
|
||||
broadcast( entity, dMeta, payload.message, entity:uid(), { "ControlDevice", "SwitchLink" } )
|
||||
end
|
||||
|
||||
-- relays a message after a delay (seconds; override with dark metadata "delay")
|
||||
_G.OSM["TrapDelay"] = function(entity, payload, dMeta)
|
||||
local msg = payload.message
|
||||
if not msg then return end
|
||||
local delay = tonumber( dMeta["delay"] ) or 2.0
|
||||
entity:queueHook( "link:Broadcast.%UID%", {
|
||||
message = msg,
|
||||
flavors = { "ControlDevice", "SwitchLink" },
|
||||
caller = entity:uid(),
|
||||
callerDarkID = dMeta["id"]
|
||||
}, delay )
|
||||
end
|
||||
|
||||
-- quest bit (QB) helpers. Bits are keyed by the dark id of the object that
|
||||
-- set them; filters gate on the bits of their incoming sources.
|
||||
local function qbSet( key, value )
|
||||
if value then _G.DarkQB[key] = true else _G.DarkQB[key] = nil end
|
||||
end
|
||||
|
||||
local function qbSatisfied( dMeta, negative )
|
||||
local incoming = dMeta["incoming_connections"] or {}
|
||||
if #incoming == 0 then return true end -- fail open if unwired
|
||||
for i = 1, #incoming do
|
||||
local conn = incoming[i]
|
||||
local key = tostring( dMeta["qb"] or conn.source_id )
|
||||
if _G.DarkQB[key] then return not negative end
|
||||
end
|
||||
return negative
|
||||
end
|
||||
|
||||
-- sets a quest bit when triggered, then relays the message
|
||||
_G.OSM["TrapQBSet"] = function(entity, payload, dMeta)
|
||||
local msg = payload.message
|
||||
local key = tostring( dMeta["id"] )
|
||||
if msg == "TurnOn" then qbSet( key, true )
|
||||
elseif msg == "TurnOff" then qbSet( key, false ) end
|
||||
broadcast( entity, dMeta, msg, entity:uid(), { "ControlDevice", "SwitchLink" } )
|
||||
end
|
||||
|
||||
-- sets a quest bit when frobb'd
|
||||
_G.OSM["FrobQB"] = {
|
||||
onFrob = function(entity, payload, dMeta)
|
||||
qbSet( tostring( dMeta["id"] ), true )
|
||||
playActivateSound( entity, dMeta )
|
||||
broadcast( entity, dMeta, "TurnOn", payload.user )
|
||||
end
|
||||
}
|
||||
|
||||
-- only relays while its quest bit is set
|
||||
_G.OSM["TrapQBFilter"] = function(entity, payload, dMeta)
|
||||
if not qbSatisfied( dMeta, false ) then return end
|
||||
broadcast( entity, dMeta, payload.message, entity:uid(), { "ControlDevice", "SwitchLink" } )
|
||||
end
|
||||
|
||||
-- only relays while its quest bit is unset
|
||||
_G.OSM["TrapQBNegFilter"] = function(entity, payload, dMeta)
|
||||
if not qbSatisfied( dMeta, true ) then return end
|
||||
broadcast( entity, dMeta, payload.message, entity:uid(), { "ControlDevice", "SwitchLink" } )
|
||||
end
|
||||
|
||||
-- relays only once all of its control inputs are on
|
||||
_G.OSM["RequireAllTrap"] = function(entity, payload, dMeta)
|
||||
local msg = payload.message
|
||||
local callerDarkID = payload.callerDarkID
|
||||
|
||||
_G.RAT_States = _G.RAT_States or {}
|
||||
local uid = entity:uid()
|
||||
_G.RAT_States[uid] = _G.RAT_States[uid] or { inputs = {}, wasOn = false }
|
||||
local state = _G.RAT_States[uid]
|
||||
|
||||
if callerDarkID then
|
||||
state.inputs[callerDarkID] = (msg == "TurnOn")
|
||||
end
|
||||
|
||||
local allOn = true
|
||||
local incoming = dMeta["incoming_connections"] or {}
|
||||
for i = 1, #incoming do
|
||||
local conn = incoming[i]
|
||||
if conn.flavor == "ControlDevice" or conn.flavor == "SwitchLink" then
|
||||
if not state.inputs[conn.source_id] then
|
||||
allOn = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if allOn and not state.wasOn then
|
||||
state.wasOn = true
|
||||
entity:callHook("link:Broadcast.%UID%", { message = "TurnOn", flavors = { "ControlDevice", "SwitchLink" }, callerDarkID = dMeta["id"], caller = uid })
|
||||
elseif not allOn and state.wasOn then
|
||||
state.wasOn = false
|
||||
entity:callHook("link:Broadcast.%UID%", { message = "TurnOff", flavors = { "ControlDevice", "SwitchLink" }, callerDarkID = dMeta["id"], caller = uid })
|
||||
end
|
||||
end
|
||||
|
||||
-- buttons / tweqs
|
||||
_G.OSM["TweqLockedButton"] = {
|
||||
onMessage = function(entity, payload, dMeta) end,
|
||||
|
||||
onFrob = function(entity, payload, dMeta)
|
||||
local eName = entity:name() or entity:uid()
|
||||
-- todo: deduce lock state
|
||||
local isLocked = true
|
||||
|
||||
print(entity, "is locked?", isLocked)
|
||||
|
||||
if isLocked then
|
||||
-- print(string.format("%s is locked! Emitting 'cardfail'.", eName))
|
||||
_G.DarkUtils.playSound(entity, "", "cardfail", { spatial = true, maxDistance = 15.0 })
|
||||
-- entity:callHook("ui:FlashMessage", { text = "Access Required: {}" })
|
||||
else
|
||||
-- unlock logic
|
||||
local cTags = (dMeta["class_tags"] or "") .. ", Event StateChange"
|
||||
_G.DarkUtils.playSound(entity, cTags, "", { spatial = true, maxDistance = 15.0 })
|
||||
|
||||
entity:callHook("link:Broadcast.%UID%", {
|
||||
message = "TurnOn", flavors = { "ControlDevice" }, caller = payload.user, callerDarkID = dMeta["id"]
|
||||
})
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
-- basic button: frob turns it on (one-shot)
|
||||
_G.OSM["BaseButton"] = {
|
||||
onFrob = function(entity, payload, dMeta)
|
||||
playActivateSound( entity, dMeta )
|
||||
broadcast( entity, dMeta, "TurnOn", payload.user )
|
||||
end
|
||||
}
|
||||
|
||||
-- toggle button: alternates TurnOn / TurnOff on each frob
|
||||
_G.OSM["TwoStateButton"] = {
|
||||
onFrob = function(entity, payload, dMeta)
|
||||
local st = getState( entity )
|
||||
st.isOn = not st.isOn
|
||||
playActivateSound( entity, dMeta )
|
||||
broadcast( entity, dMeta, st.isOn and "TurnOn" or "TurnOff", payload.user )
|
||||
end
|
||||
}
|
||||
|
||||
-- depressable tweq: frob latches it on
|
||||
_G.OSM["TweqDepressable"] = {
|
||||
onFrob = function(entity, payload, dMeta)
|
||||
playActivateSound( entity, dMeta )
|
||||
broadcast( entity, dMeta, "TurnOn", payload.user )
|
||||
end
|
||||
}
|
||||
|
||||
-- toggle tweq: frob or message flips its state and relays it
|
||||
_G.OSM["TrapTweq"] = {
|
||||
onMessage = function(entity, payload, dMeta)
|
||||
local st = getState( entity )
|
||||
st.isOn = (payload.message == "TurnOn")
|
||||
broadcast( entity, dMeta, payload.message, payload.caller or entity:uid(), { "ControlDevice", "SwitchLink" } )
|
||||
end,
|
||||
|
||||
onFrob = function(entity, payload, dMeta)
|
||||
local st = getState( entity )
|
||||
st.isOn = not st.isOn
|
||||
playActivateSound( entity, dMeta )
|
||||
broadcast( entity, dMeta, st.isOn and "TurnOn" or "TurnOff", payload.user )
|
||||
end
|
||||
}
|
||||
|
||||
-- doors
|
||||
-- standard door: frob toggles it open/closed (door.lua performs the motion)
|
||||
_G.OSM["StdDoor"] = {
|
||||
onMessage = function(entity, payload, dMeta)
|
||||
local st = getState( entity )
|
||||
if payload.message == "TurnOn" then st.isOpen = true
|
||||
elseif payload.message == "TurnOff" then st.isOpen = false end
|
||||
end,
|
||||
|
||||
onFrob = function(entity, payload, dMeta)
|
||||
local st = getState( entity )
|
||||
if st.isOpen == nil then
|
||||
st.isOpen = (tonumber( (dMeta["door"] or {})["status"] ) == 1)
|
||||
end
|
||||
st.isOpen = not st.isOpen
|
||||
playActivateSound( entity, dMeta )
|
||||
broadcast( entity, dMeta, st.isOpen and "TurnOn" or "TurnOff", payload.user )
|
||||
end
|
||||
}
|
||||
|
||||
-- lights
|
||||
-- find the light child node created by the level loader ("{name}_light")
|
||||
local function getLightComponent( entity )
|
||||
local lightEnt = entity:findByName( (entity:name() or "") .. "_light" )
|
||||
if not lightEnt then return nil end
|
||||
return lightEnt:getComponent("LightBehavior::Metadata")
|
||||
end
|
||||
|
||||
local function setLightPower( entity, on )
|
||||
local st = getState( entity )
|
||||
local lightComp = getLightComponent( entity )
|
||||
if not lightComp then return end
|
||||
if st.lightPower == nil then st.lightPower = lightComp.power end
|
||||
lightComp.power = on and st.lightPower or 0.0
|
||||
end
|
||||
|
||||
-- switchable light: TurnOn/TurnOff (or frob) toggles its power
|
||||
_G.OSM["BaseLight"] = {
|
||||
onMessage = function(entity, payload, dMeta)
|
||||
if payload.message == "TurnOn" then setLightPower( entity, true )
|
||||
elseif payload.message == "TurnOff" then setLightPower( entity, false ) end
|
||||
end,
|
||||
|
||||
onFrob = function(entity, payload, dMeta)
|
||||
local st = getState( entity )
|
||||
st.isOn = not st.isOn
|
||||
playActivateSound( entity, dMeta )
|
||||
setLightPower( entity, st.isOn )
|
||||
broadcast( entity, dMeta, st.isOn and "TurnOn" or "TurnOff", payload.user )
|
||||
end
|
||||
}
|
||||
|
||||
-- light that hums while it is on
|
||||
_G.OSM["LightSoundOn"] = {
|
||||
onMessage = function(entity, payload, dMeta)
|
||||
if payload.message == "TurnOn" then
|
||||
setLightPower( entity, true )
|
||||
local st = getState( entity )
|
||||
if not st.isSounding then
|
||||
local soundMeta = dMeta["sound"] or {}
|
||||
local explicitSchema = soundMeta["schema"] or ""
|
||||
local played = _G.DarkUtils.playSound( entity, "", explicitSchema, { spatial = true, loop = true, unique = true } )
|
||||
if not played then
|
||||
played = _G.DarkUtils.playSound( entity, (dMeta["class_tags"] or "") .. ", Event Activate", nil, { spatial = true, loop = true, unique = true } )
|
||||
end
|
||||
st.isSounding = played ~= nil
|
||||
end
|
||||
elseif payload.message == "TurnOff" then
|
||||
setLightPower( entity, false )
|
||||
local st = getState( entity )
|
||||
if st.isSounding then
|
||||
st.isSounding = nil
|
||||
entity:callHook( "sound:Stop.%UID%", {} )
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
onFrob = function(entity, payload, dMeta)
|
||||
local st = getState( entity )
|
||||
st.isOn = not st.isOn
|
||||
playActivateSound( entity, dMeta )
|
||||
setLightPower( entity, st.isOn )
|
||||
broadcast( entity, dMeta, st.isOn and "TurnOn" or "TurnOff", payload.user )
|
||||
end
|
||||
}
|
||||
|
||||
-- gravity zones
|
||||
local function gravityTick( self, factor )
|
||||
local body = self:getComponent("PhysicsBody")
|
||||
if not body or not body:initialized() then return end
|
||||
|
||||
local st = getState( self )
|
||||
local affected = st.gravAffected or {}
|
||||
st.gravAffected = affected
|
||||
local current = {}
|
||||
|
||||
local events = body:getCollisionEvents()
|
||||
for i = 1, #events do
|
||||
local event = events[i]
|
||||
local otherBody
|
||||
if event.a:getObject():uid() == self:uid() then
|
||||
otherBody = event.b
|
||||
elseif event.b:getObject():uid() == self:uid() then
|
||||
otherBody = event.a
|
||||
end
|
||||
|
||||
if otherBody then
|
||||
local otherEnt = otherBody:getObject()
|
||||
local uid = otherEnt:uid()
|
||||
if uid ~= self:uid() and otherBody:getMass() > 0.0 then
|
||||
current[uid] = true
|
||||
if not affected[uid] then
|
||||
otherBody:setGravity( Vector3f( 0, -9.81 * factor, 0 ) )
|
||||
affected[uid] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for uid, _ in pairs( affected ) do
|
||||
if not current[uid] then
|
||||
local otherEnt = entities.get(uid)
|
||||
if otherEnt and otherEnt:uid() then
|
||||
local otherBody = otherEnt:getComponent("PhysicsBody")
|
||||
if otherBody and otherBody:initialized() then
|
||||
otherBody:enableGravity( true )
|
||||
end
|
||||
end
|
||||
affected[uid] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- destruction / teleport / message traps
|
||||
local function destroyTick( self )
|
||||
local body = self:getComponent("PhysicsBody")
|
||||
if not body or not body:initialized() then return end
|
||||
|
||||
local events = body:getCollisionEvents()
|
||||
for i = 1, #events do
|
||||
local event = events[i]
|
||||
local otherBody
|
||||
if event.a:getObject():uid() == self:uid() then
|
||||
otherBody = event.b
|
||||
elseif event.b:getObject():uid() == self:uid() then
|
||||
otherBody = event.a
|
||||
end
|
||||
|
||||
if otherBody then
|
||||
local otherEnt = otherBody:getObject()
|
||||
if otherEnt:name() ~= "Player" and otherBody:getMass() > 0.0 then
|
||||
entities.destroy( otherEnt )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- destroys dynamic objects that touch it
|
||||
_G.OSM["TriggerDestroy"] = function(entity, payload, dMeta) end
|
||||
_G.OSM["TrapDestroyer"] = _G.OSM["TriggerDestroy"]
|
||||
_G.OSM["TrapDestroy"] = _G.OSM["TriggerDestroy"]
|
||||
|
||||
-- on trigger, destroys every object whose name matches its target connection
|
||||
local function destroyAllMatching( entity, dMeta )
|
||||
local conn, _ = findConnection( dMeta, "" )
|
||||
if not conn or not conn.target_node then return end
|
||||
local pattern = conn.target_node
|
||||
|
||||
for i, victim in ipairs( entities.all() ) do
|
||||
if victim and victim:uid() and victim:uid() ~= entity:uid() and victim:name() == pattern then
|
||||
entities.destroy( victim )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
_G.OSM["TrapTerminator"] = {
|
||||
onMessage = function(entity, payload, dMeta)
|
||||
if payload.message == "TurnOn" or payload.message == "Toggle" then
|
||||
destroyAllMatching( entity, dMeta )
|
||||
end
|
||||
end,
|
||||
onFrob = function(entity, payload, dMeta)
|
||||
playActivateSound( entity, dMeta )
|
||||
destroyAllMatching( entity, dMeta )
|
||||
end
|
||||
}
|
||||
|
||||
_G.OSM["DestroyAllByName"] = _G.OSM["TrapTerminator"]
|
||||
|
||||
-- teleports the player to its teleport target on trigger or frob
|
||||
local function teleportPlayer( entity, dMeta )
|
||||
local conn, targetEnt = findConnection( dMeta, "Tele" )
|
||||
if not targetEnt then return end
|
||||
|
||||
local player = entities.controller()
|
||||
if not player or not player:uid() then return end
|
||||
|
||||
local playerTransform = player:getComponent("Transform")
|
||||
local targetTransform = targetEnt:getComponent("Transform")
|
||||
playerTransform.position = targetTransform.position
|
||||
playerTransform.orientation = targetTransform.orientation
|
||||
end
|
||||
|
||||
_G.OSM["TrapTeleport"] = {
|
||||
onMessage = function(entity, payload, dMeta)
|
||||
if payload.message == "TurnOn" or payload.message == "Toggle" then
|
||||
teleportPlayer( entity, dMeta )
|
||||
end
|
||||
end,
|
||||
onFrob = function(entity, payload, dMeta)
|
||||
playActivateSound( entity, dMeta )
|
||||
teleportPlayer( entity, dMeta )
|
||||
end
|
||||
}
|
||||
|
||||
-- flashes a text message to the player on trigger or frob
|
||||
_G.OSM["TrapMessage"] = {
|
||||
onMessage = function(entity, payload, dMeta)
|
||||
local text = dMeta["message"] or (entity:name() or "")
|
||||
print( "[OSM] TrapMessage: " .. tostring(text) )
|
||||
entity:callHook( "ui:FlashMessage", { text = text } )
|
||||
end,
|
||||
onFrob = function(entity, payload, dMeta)
|
||||
local text = dMeta["message"] or (entity:name() or "")
|
||||
print( "[OSM] TrapMessage: " .. tostring(text) )
|
||||
entity:callHook( "ui:FlashMessage", { text = text } )
|
||||
end
|
||||
}
|
||||
|
||||
-- elevators
|
||||
_G.OSM["ElevatorButton"] = _G.OSM["RelayTrap"]
|
||||
_G.OSM["RerouteElevatorButton"] = _G.OSM["RelayTrap"]
|
||||
|
||||
_G.OSM["BaseElevator"] = function(entity, payload, dMeta)
|
||||
local msg = payload.message
|
||||
local eName = entity:name() or entity:uid()
|
||||
|
||||
-- print(string.format("%s (Elevator) received command: %s", eName, msg))
|
||||
|
||||
if msg == "TurnOn" or msg == "TurnOff" then
|
||||
-- print(string.format("--> Commanding Lift '%s' to move!", eName))
|
||||
end
|
||||
end
|
||||
|
||||
_G.OSM["Elevator"] = _G.OSM["BaseElevator"]
|
||||
_G.OSM["OldStyleBaseElevator"] = _G.OSM["BaseElevator"]
|
||||
|
||||
-- case variants found in level data (to-do: case insensitive mappings?)
|
||||
_G.OSM["basebutton"] = _G.OSM["BaseButton"]
|
||||
_G.OSM["trapdelay"] = _G.OSM["TrapDelay"]
|
||||
_G.OSM["triggerdestroy"] = _G.OSM["TriggerDestroy"]
|
||||
_G.OSM["Trapterminator"] = _G.OSM["TrapTerminator"]
|
||||
_G.OSM["LIghtSoundOn"] = _G.OSM["LightSoundOn"]
|
||||
_G.OSM["tweqtrap"] = _G.OSM["TrapTweq"]
|
||||
_G.OSM["tweqbutton"] = _G.OSM["BaseButton"]
|
||||
_G.OSM["Tweqable"] = _G.OSM["TrapTweq"]
|
||||
|
||||
-- per-object tick binding (only for scripts that need one)
|
||||
local metadata = ent:getComponent("Metadata")
|
||||
local darkMeta = metadata["dark"] or {}
|
||||
local myScripts = darkMeta["scripts"] or {}
|
||||
|
||||
for i = 1, #myScripts do
|
||||
local script = myScripts[i]
|
||||
if script == "TrapGravity" or script == "ZeroGravRoom" then
|
||||
local factor = tonumber( darkMeta["gravity"] )
|
||||
if factor == nil then factor = 0.0 end
|
||||
ent:bind( "tick", function(self) gravityTick( self, factor ) end )
|
||||
elseif script == "TriggerDestroy" or script == "triggerdestroy" or script == "TrapDestroyer" or script == "TrapDestroy" then
|
||||
ent:bind( "tick", function(self) destroyTick( self ) end )
|
||||
end
|
||||
end
|
||||
|
||||
42
bin/data/entities/scripts/valve/breakable.lua
Normal file
42
bin/data/entities/scripts/valve/breakable.lua
Normal file
@ -0,0 +1,42 @@
|
||||
local ent = ent
|
||||
local metadata = ent:getComponent("Metadata")
|
||||
local metadataValve = metadata["valve"] or {}
|
||||
local physicsBody = ent:getComponent("PhysicsBody")
|
||||
|
||||
local health = tonumber(metadataValve["health"]) or 100.0
|
||||
local flags = tonumber(metadataValve["spawnflags"]) or 0
|
||||
local unbreakable = (math.floor(flags / 1) % 2) ~= 0
|
||||
local damageScale = tonumber(metadataValve["damagescale"]) or 1.0
|
||||
|
||||
local alive = true
|
||||
|
||||
local function destroy()
|
||||
if not alive then return end
|
||||
alive = false
|
||||
ent:queueHook("io:FireOutput.%UID%", { output = "OnBreak" }, 0)
|
||||
entities.destroy(ent)
|
||||
end
|
||||
|
||||
ent:bind( "tick", function(self)
|
||||
if not alive or unbreakable then return end
|
||||
if not physicsBody:initialized() then return end
|
||||
|
||||
local collisionEvents = physicsBody:getCollisionEvents()
|
||||
for i, event in ipairs(collisionEvents) do
|
||||
if event.impulse > 1.0 then
|
||||
health = health - (event.impulse * damageScale)
|
||||
if health <= 0 then
|
||||
destroy()
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end )
|
||||
|
||||
ent:addHook("io:Input.%UID%", function( payload )
|
||||
local input = payload.input
|
||||
|
||||
if input == "Kill" or input == "Break" then
|
||||
destroy()
|
||||
end
|
||||
end)
|
||||
124
bin/data/entities/scripts/valve/button.lua
Normal file
124
bin/data/entities/scripts/valve/button.lua
Normal file
@ -0,0 +1,124 @@
|
||||
local ent = ent
|
||||
local metadata = ent:getComponent("Metadata")
|
||||
local metadataValve = metadata["valve"] or {}
|
||||
|
||||
local timer = Timer.new()
|
||||
if not timer:running() then
|
||||
timer:start()
|
||||
end
|
||||
|
||||
local SOURCE_TO_METERS = 0.07
|
||||
|
||||
local transform = ent:getComponent("Transform")
|
||||
local physicsBody = ent:getComponent("PhysicsBody")
|
||||
|
||||
local lip = (tonumber(metadataValve["lip"]) or 8.0) * SOURCE_TO_METERS
|
||||
local wait = tonumber(metadataValve["wait"]) or 1.0
|
||||
local flags = tonumber(metadataValve["spawnflags"]) or 0
|
||||
|
||||
-- movement direction: movedir vector, else angle keyvalue, else local up
|
||||
local moveDir = Vector3f(0, 1, 0)
|
||||
local movedir = metadataValve["movedir"]
|
||||
if type(movedir) == "table" then
|
||||
moveDir = Vector3f(movedir[1], movedir[2], movedir[3]):normalize()
|
||||
elseif type(movedir) == "string" and movedir ~= "" then
|
||||
local x, y, z = movedir:match("^%s*(-?%d+%.?%d*)%s+(-?%d+%.?%d*)%s+(-?%d+%.?%d*)%s*$")
|
||||
if x then
|
||||
moveDir = Vector3f(tonumber(x), tonumber(y), tonumber(z)):normalize()
|
||||
end
|
||||
else
|
||||
local angle = tonumber(metadataValve["angle"])
|
||||
if angle then
|
||||
if angle == -1 then
|
||||
moveDir = Vector3f(0, 1, 0)
|
||||
elseif angle == -2 then
|
||||
moveDir = Vector3f(0, -1, 0)
|
||||
else
|
||||
local yaw = math.rad(angle)
|
||||
moveDir = Vector3f(math.cos(yaw), 0.0, -math.sin(yaw))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local pressTime = 0.5
|
||||
if wait > 0 then
|
||||
pressTime = math.max(0.25, math.min(wait, 2.0))
|
||||
end
|
||||
local speed = lip / pressTime
|
||||
|
||||
-- 0 = idle, 1 = pressing out, 2 = held, 3 = retracting
|
||||
local state = 0
|
||||
local currentDistance = 0
|
||||
|
||||
local function press()
|
||||
if state ~= 0 then return end
|
||||
state = 1
|
||||
ent:queueHook("io:FireOutput.%UID%", { output = "OnPressed" }, 0)
|
||||
ent:queueHook("io:FireOutput.%UID%", { output = "OnUsed" }, 0)
|
||||
end
|
||||
|
||||
local function release()
|
||||
if state == 2 then
|
||||
state = 3
|
||||
end
|
||||
end
|
||||
|
||||
ent:bind( "tick", function(self)
|
||||
if state == 1 then
|
||||
local remaining = lip - currentDistance
|
||||
local move = math.min(time.delta() * speed, remaining)
|
||||
currentDistance = currentDistance + move
|
||||
transform.position = transform.position + moveDir * move
|
||||
|
||||
if currentDistance >= lip then
|
||||
state = 2
|
||||
timer:reset()
|
||||
end
|
||||
elseif state == 3 then
|
||||
local move = math.min(time.delta() * speed, currentDistance)
|
||||
currentDistance = currentDistance - move
|
||||
transform.position = transform.position - moveDir * move
|
||||
|
||||
if currentDistance <= 0 then
|
||||
state = 0
|
||||
ent:queueHook("io:FireOutput.%UID%", { output = "OnUnpressed" }, 0)
|
||||
end
|
||||
elseif state == 2 and wait > 0 then
|
||||
if timer:elapsed() >= wait then
|
||||
state = 3
|
||||
end
|
||||
end
|
||||
|
||||
-- press when touched (floor buttons)
|
||||
if state == 0 and physicsBody:initialized() then
|
||||
local collisionEvents = physicsBody:getCollisionEvents()
|
||||
for i, event in ipairs(collisionEvents) do
|
||||
local other = nil
|
||||
if event.a:getObject():uid() == ent:uid() then
|
||||
other = event.b
|
||||
elseif event.b:getObject():uid() == ent:uid() then
|
||||
other = event.a
|
||||
end
|
||||
|
||||
if other then
|
||||
press()
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end )
|
||||
|
||||
ent:addHook( "entity:Use.%UID%", function( payload )
|
||||
if payload.user == ent:uid() then return end
|
||||
press()
|
||||
end )
|
||||
|
||||
ent:addHook("io:Input.%UID%", function( payload )
|
||||
local input = payload.input
|
||||
|
||||
if input == "Press" or input == "Down" then
|
||||
press()
|
||||
elseif input == "Release" or input == "Up" or input == "Reset" then
|
||||
release()
|
||||
end
|
||||
end)
|
||||
25
bin/data/entities/scripts/valve/case.lua
Normal file
25
bin/data/entities/scripts/valve/case.lua
Normal file
@ -0,0 +1,25 @@
|
||||
local ent = ent
|
||||
local metadata = ent:getComponent("Metadata")
|
||||
local metadataValve = metadata["valve"] or {}
|
||||
|
||||
local cases = {}
|
||||
for i = 1, 20 do
|
||||
local value = metadataValve["Case" .. i]
|
||||
if value ~= nil and tostring(value) ~= "" then
|
||||
cases[i] = tostring(value)
|
||||
end
|
||||
end
|
||||
|
||||
ent:addHook("io:Input.%UID%", function( payload )
|
||||
local param = payload.parameter
|
||||
local p = (param == nil) and "" or tostring(param)
|
||||
|
||||
for i = 1, 20 do
|
||||
if cases[i] then
|
||||
if cases[i] == p or tonumber(cases[i]) == tonumber(p) then
|
||||
ent:callHook("io:FireOutput.%UID%", { output = "Case" .. i })
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
37
bin/data/entities/scripts/valve/counter.lua
Normal file
37
bin/data/entities/scripts/valve/counter.lua
Normal file
@ -0,0 +1,37 @@
|
||||
local ent = ent
|
||||
local metadata = ent:getComponent("Metadata")
|
||||
local metadataValve = metadata["valve"] or {}
|
||||
|
||||
local startCount = tonumber(metadataValve["StartCount"]) or 0
|
||||
local tripCount = tonumber(metadataValve["TripCount"]) or 10
|
||||
local flags = tonumber(metadataValve["spawnflags"]) or 0
|
||||
|
||||
local enabled = (math.floor(flags / 1) % 2) == 0
|
||||
local count = startCount
|
||||
|
||||
ent:addHook("io:Input.%UID%", function( payload )
|
||||
if not enabled then return end
|
||||
|
||||
local input = payload.input
|
||||
|
||||
if input == "Increment" then
|
||||
count = count + 1
|
||||
if count >= tripCount then
|
||||
ent:callHook("io:FireOutput.%UID%", { output = "OnHigh" })
|
||||
count = startCount
|
||||
end
|
||||
elseif input == "Decrement" then
|
||||
count = count - 1
|
||||
if count < startCount then
|
||||
ent:callHook("io:FireOutput.%UID%", { output = "OnLow" })
|
||||
count = startCount
|
||||
end
|
||||
elseif input == "Reset" then
|
||||
count = startCount
|
||||
elseif input == "Set" then
|
||||
local value = tonumber(payload.parameter)
|
||||
if value then
|
||||
count = math.floor(value)
|
||||
end
|
||||
end
|
||||
end)
|
||||
56
bin/data/entities/scripts/valve/hurt.lua
Normal file
56
bin/data/entities/scripts/valve/hurt.lua
Normal file
@ -0,0 +1,56 @@
|
||||
local ent = ent
|
||||
local metadata = ent:getComponent("Metadata")
|
||||
local metadataValve = metadata["valve"] or {}
|
||||
local physicsBody = ent:getComponent("PhysicsBody")
|
||||
|
||||
local damage = tonumber(metadataValve["damage"]) or 1.0
|
||||
local delay = tonumber(metadataValve["delay"]) or 0.5
|
||||
|
||||
-- note: the engine has no health/damage system yet, so this trigger only
|
||||
-- reports contact via I/O outputs (OnEntityTouch / OnTrigger / OnEndTouch)
|
||||
|
||||
local touching = {}
|
||||
|
||||
local function entityKey( body )
|
||||
local object = body:getObject()
|
||||
local meta = object:getComponent("Metadata")
|
||||
local valve = meta["valve"] or {}
|
||||
if valve["targetname"] then
|
||||
return tostring(valve["targetname"])
|
||||
end
|
||||
return tostring(object:uid())
|
||||
end
|
||||
|
||||
ent:bind( "tick", function(self)
|
||||
if not physicsBody:initialized() then return end
|
||||
|
||||
local currentCollisions = {}
|
||||
local collisionEvents = physicsBody:getCollisionEvents()
|
||||
|
||||
for i, event in ipairs(collisionEvents) do
|
||||
local other = nil
|
||||
if event.a:getObject():uid() == ent:uid() then
|
||||
other = event.b
|
||||
elseif event.b:getObject():uid() == ent:uid() then
|
||||
other = event.a
|
||||
end
|
||||
|
||||
if other then
|
||||
local key = entityKey(other)
|
||||
currentCollisions[key] = true
|
||||
|
||||
if not touching[key] then
|
||||
touching[key] = true
|
||||
ent:queueHook("io:FireOutput.%UID%", { output = "OnEntityTouch", parameter = key }, 0)
|
||||
ent:queueHook("io:FireOutput.%UID%", { output = "OnTrigger" }, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for key, _ in pairs(touching) do
|
||||
if not currentCollisions[key] then
|
||||
touching[key] = nil
|
||||
ent:queueHook("io:FireOutput.%UID%", { output = "OnEndTouch", parameter = key }, 0)
|
||||
end
|
||||
end
|
||||
end )
|
||||
34
bin/data/entities/scripts/valve/ladder.lua
Normal file
34
bin/data/entities/scripts/valve/ladder.lua
Normal file
@ -0,0 +1,34 @@
|
||||
local ent = ent
|
||||
local metadata = ent:getComponent("Metadata")
|
||||
local metadataValve = metadata["valve"] or {}
|
||||
local physicsBody = ent:getComponent("PhysicsBody")
|
||||
|
||||
local controller = entities.controller()
|
||||
local controllerBody = controller:getComponent("PhysicsBody")
|
||||
|
||||
local climbSpeed = 2.0
|
||||
|
||||
ent:bind( "tick", function(self)
|
||||
if not physicsBody:initialized() then return end
|
||||
if not controllerBody:initialized() then return end
|
||||
|
||||
local box = physicsBody:bounds()
|
||||
local pos = controllerBody:getTransform().position
|
||||
|
||||
local inside =
|
||||
pos.x >= box.min.x - 0.5 and pos.x <= box.max.x + 0.5 and
|
||||
pos.z >= box.min.z - 0.5 and pos.z <= box.max.z + 0.5 and
|
||||
pos.y >= box.min.y - 0.5 and pos.y <= box.max.y + 1.0
|
||||
|
||||
if not inside then return end
|
||||
|
||||
local velocity = controllerBody:getVelocity()
|
||||
|
||||
if window.keyPressed("Space") or window.keyPressed("W") then
|
||||
velocity.y = climbSpeed
|
||||
elseif window.keyPressed("S") then
|
||||
velocity.y = -climbSpeed
|
||||
end
|
||||
|
||||
controllerBody:setVelocity(velocity)
|
||||
end )
|
||||
43
bin/data/entities/scripts/valve/relay.lua
Normal file
43
bin/data/entities/scripts/valve/relay.lua
Normal file
@ -0,0 +1,43 @@
|
||||
local ent = ent
|
||||
local metadata = ent:getComponent("Metadata")
|
||||
local metadataValve = metadata["valve"] or {}
|
||||
|
||||
local timer = Timer.new()
|
||||
if not timer:running() then
|
||||
timer:start()
|
||||
end
|
||||
|
||||
local delay = tonumber(metadataValve["delay"]) or 0.0
|
||||
local enabled = true
|
||||
|
||||
local pendingOutputs = {}
|
||||
|
||||
ent:addHook("io:Input.%UID%", function( payload )
|
||||
local input = payload.input
|
||||
|
||||
if input == "Enable" then
|
||||
enabled = true
|
||||
elseif input == "Disable" then
|
||||
enabled = false
|
||||
elseif input == "Toggle" then
|
||||
enabled = not enabled
|
||||
else
|
||||
if not enabled then return end
|
||||
|
||||
table.insert(pendingOutputs, {
|
||||
fireTime = timer:elapsed() + math.max(0.0, delay),
|
||||
input = input,
|
||||
parameter = payload.parameter
|
||||
})
|
||||
end
|
||||
end)
|
||||
|
||||
ent:bind( "tick", function(self)
|
||||
for i = #pendingOutputs, 1, -1 do
|
||||
local job = pendingOutputs[i]
|
||||
if timer:elapsed() >= job.fireTime then
|
||||
ent:queueHook("io:FireOutput.%UID%", { output = job.input, parameter = job.parameter }, 0)
|
||||
table.remove(pendingOutputs, i)
|
||||
end
|
||||
end
|
||||
end )
|
||||
55
bin/data/entities/scripts/valve/timer.lua
Normal file
55
bin/data/entities/scripts/valve/timer.lua
Normal file
@ -0,0 +1,55 @@
|
||||
local ent = ent
|
||||
local metadata = ent:getComponent("Metadata")
|
||||
local metadataValve = metadata["valve"] or {}
|
||||
|
||||
local timer = Timer.new()
|
||||
if not timer:running() then
|
||||
timer:start()
|
||||
end
|
||||
|
||||
local refireTime = tonumber(metadataValve["RefireTime"]) or 10.0
|
||||
local randomRange = tonumber(metadataValve["random"]) or 0.0
|
||||
local flags = tonumber(metadataValve["spawnflags"]) or 0
|
||||
|
||||
local startDisabled = (tonumber(metadataValve["StartDisabled"]) or 0) ~= 0
|
||||
if not startDisabled and (math.floor(flags / 1) % 2) ~= 0 then
|
||||
startDisabled = true
|
||||
end
|
||||
|
||||
local enabled = not startDisabled
|
||||
local nextFire = refireTime
|
||||
|
||||
local function interval()
|
||||
local value = refireTime
|
||||
if randomRange > 0 then
|
||||
value = value + (math.random() * 2.0 - 1.0) * randomRange
|
||||
end
|
||||
return math.max(0.01, value)
|
||||
end
|
||||
|
||||
ent:bind( "tick", function(self)
|
||||
if not enabled then return end
|
||||
|
||||
if timer:elapsed() >= nextFire then
|
||||
ent:queueHook("io:FireOutput.%UID%", { output = "OnTimer" }, 0)
|
||||
nextFire = timer:elapsed() + interval()
|
||||
end
|
||||
end )
|
||||
|
||||
ent:addHook("io:Input.%UID%", function( payload )
|
||||
local input = payload.input
|
||||
|
||||
if input == "Enable" then
|
||||
enabled = true
|
||||
nextFire = timer:elapsed() + refireTime
|
||||
elseif input == "Disable" then
|
||||
enabled = false
|
||||
elseif input == "Toggle" then
|
||||
enabled = not enabled
|
||||
if enabled then
|
||||
nextFire = timer:elapsed() + refireTime
|
||||
end
|
||||
elseif input == "Reset" then
|
||||
nextFire = timer:elapsed() + refireTime
|
||||
end
|
||||
end)
|
||||
95
bin/data/entities/scripts/valve/train.lua
Normal file
95
bin/data/entities/scripts/valve/train.lua
Normal file
@ -0,0 +1,95 @@
|
||||
local ent = ent
|
||||
local metadata = ent:getComponent("Metadata")
|
||||
local metadataValve = metadata["valve"] or {}
|
||||
|
||||
local SOURCE_TO_METERS = 0.07
|
||||
|
||||
local transform = ent:getComponent("Transform")
|
||||
local physicsBody = ent:getComponent("PhysicsBody")
|
||||
|
||||
local speed = (tonumber(metadataValve["speed"]) or 200.0) * SOURCE_TO_METERS
|
||||
local targetName = metadataValve["target"]
|
||||
|
||||
-- collect waypoints from path_corner entities chained via their target keyvalue
|
||||
local waypoints = {}
|
||||
local hasLoop = false
|
||||
if type(targetName) == "string" and targetName ~= "" then
|
||||
local cornerByName = {}
|
||||
for i, e in ipairs(entities.all()) do
|
||||
if e:name() == "path_corner" then
|
||||
local meta = e:getComponent("Metadata")
|
||||
local valve = meta["valve"] or {}
|
||||
local tname = valve["targetname"]
|
||||
if tname then
|
||||
cornerByName[tname] = e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local current = cornerByName[targetName]
|
||||
local firstUid = nil
|
||||
local visited = {}
|
||||
while current and not visited[current:uid()] do
|
||||
visited[current:uid()] = true
|
||||
if firstUid == nil then firstUid = current:uid() end
|
||||
table.insert(waypoints, current:getComponent("Transform").position)
|
||||
|
||||
local meta = current:getComponent("Metadata")
|
||||
local valve = meta["valve"] or {}
|
||||
current = cornerByName[valve["target"]]
|
||||
end
|
||||
hasLoop = (current ~= nil and current:uid() == firstUid)
|
||||
end
|
||||
|
||||
-- 0 = idle, 1 = moving
|
||||
local state = (#waypoints >= 2) and 1 or 0
|
||||
local waypointIndex = 1
|
||||
local lastPosition = transform.position
|
||||
|
||||
ent:bind( "tick", function(self)
|
||||
if state ~= 1 then return end
|
||||
|
||||
local target = waypoints[waypointIndex]
|
||||
local toTarget = target - transform.position
|
||||
local dist = math.sqrt(toTarget.x * toTarget.x + toTarget.y * toTarget.y + toTarget.z * toTarget.z)
|
||||
local step = time.delta() * speed
|
||||
|
||||
if dist <= step then
|
||||
transform.position = target
|
||||
waypointIndex = waypointIndex + 1
|
||||
if waypointIndex > #waypoints then
|
||||
if hasLoop then
|
||||
waypointIndex = 1
|
||||
else
|
||||
state = 0
|
||||
end
|
||||
end
|
||||
else
|
||||
local dir = toTarget / dist
|
||||
transform.position = transform.position + dir * step
|
||||
end
|
||||
|
||||
-- carry passengers riding on the train
|
||||
local delta = transform.position - lastPosition
|
||||
lastPosition = transform.position
|
||||
|
||||
if physicsBody:initialized() then
|
||||
local box = physicsBody:bounds()
|
||||
for i, e in ipairs(entities.all()) do
|
||||
if e:uid() ~= ent:uid() then
|
||||
local body = e:getComponent("PhysicsBody")
|
||||
if body:initialized() then
|
||||
local pos = body:getTransform().position
|
||||
local riding =
|
||||
pos.x >= box.min.x - 0.6 and pos.x <= box.max.x + 0.6 and
|
||||
pos.z >= box.min.z - 0.6 and pos.z <= box.max.z + 0.6 and
|
||||
pos.y >= box.min.y - 0.5 and pos.y <= box.max.y + 2.5
|
||||
if riding then
|
||||
local t = e:getComponent("Transform")
|
||||
t.position = t.position + delta
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end )
|
||||
@ -125,6 +125,14 @@ namespace ext {
|
||||
|
||||
uf::stl::unordered_map<VkCommandBuffer, pod::Checkpoint*> checkpoints;
|
||||
|
||||
// queue API calls (submit/waitIdle/present/checkpoint reads) must not overlap on the same VkQueue, even from different threads
|
||||
struct QueueLocks {
|
||||
std::mutex mutex;
|
||||
uf::stl::unordered_map<VkQueue, std::unique_ptr<std::mutex>> locks;
|
||||
uf::stl::unordered_map<VkCommandPool, std::unique_ptr<std::mutex>> poolLocks;
|
||||
};
|
||||
std::unique_ptr<QueueLocks> queueLocks;
|
||||
|
||||
uf::Window* window;
|
||||
|
||||
struct QueueFamilyIndices {
|
||||
@ -176,6 +184,8 @@ namespace ext {
|
||||
VkCommandPool getCommandPool( QueueEnum );
|
||||
VkQueue getQueue( QueueEnum, uf::thread::id_t );
|
||||
VkCommandPool getCommandPool( QueueEnum, uf::thread::id_t );
|
||||
std::unique_lock<std::mutex> lockQueue( VkQueue queue );
|
||||
std::unique_lock<std::mutex> lockPool( VkCommandPool pool );
|
||||
|
||||
// RAII
|
||||
void initialize();
|
||||
|
||||
@ -484,6 +484,7 @@ void ext::VoxelizerSceneBehavior::destroy( uf::Object& self ){
|
||||
texture.device = &uf::renderer::device;
|
||||
texture.view = view;
|
||||
}
|
||||
metadata.views.clear();
|
||||
}
|
||||
void ext::VoxelizerSceneBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ) {
|
||||
serializer["vxgi"]["size"] = /*this->*/voxelSize.x;
|
||||
|
||||
@ -1422,6 +1422,52 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
|
||||
node.metadata["holdable"] = (mass <= 35.0f) && !motionDisabled && !preventPickup;
|
||||
}
|
||||
// bind button
|
||||
else if ( node.name == "func_button" ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/valve/button.lua");
|
||||
// signal to assign a physics body
|
||||
if ( ext::json::isNull( node.metadata["physics"] ) ) {
|
||||
node.metadata["physics"]["type"] = "bounding box";
|
||||
}
|
||||
}
|
||||
// bind train / moving platform
|
||||
else if ( node.name == "func_train" || node.name == "func_track" ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/valve/train.lua");
|
||||
// signal to assign a physics body
|
||||
if ( ext::json::isNull( node.metadata["physics"] ) ) {
|
||||
node.metadata["physics"]["type"] = "bounding box";
|
||||
}
|
||||
}
|
||||
// bind breakable
|
||||
else if ( node.name == "func_breakable" || node.name.starts_with("breakable_") ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/valve/breakable.lua");
|
||||
// signal to assign a physics body
|
||||
if ( ext::json::isNull( node.metadata["physics"] ) ) {
|
||||
node.metadata["physics"]["type"] = "mesh";
|
||||
node.metadata["physics"]["category"] = "static";
|
||||
}
|
||||
}
|
||||
// bind ladder
|
||||
else if ( node.name == "func_ladder" ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/valve/ladder.lua");
|
||||
// signal to assign a physics body
|
||||
if ( ext::json::isNull( node.metadata["physics"] ) ) {
|
||||
node.metadata["physics"]["type"] = "bounding box";
|
||||
node.metadata["physics"]["category"] = "trigger";
|
||||
}
|
||||
}
|
||||
// bind logic entities (pure I/O, no physics body)
|
||||
else if ( node.name.starts_with("logic_") ) {
|
||||
if ( node.name == "logic_timer" ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/valve/timer.lua");
|
||||
} else if ( node.name == "logic_relay" ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/valve/relay.lua");
|
||||
} else if ( node.name == "logic_counter" ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/valve/counter.lua");
|
||||
} else if ( node.name == "logic_case" ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/valve/case.lua");
|
||||
}
|
||||
}
|
||||
// assume all other funcs are to have a physics body
|
||||
else if ( node.name.starts_with("func_") ) {
|
||||
if ( ext::json::isNull( node.metadata["physics"] ) ) {
|
||||
@ -1433,7 +1479,11 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
|
||||
// check if trigger
|
||||
if ( node.name.starts_with("trigger_") ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/valve/trigger.lua");
|
||||
if ( node.name == "trigger_hurt" ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/valve/hurt.lua");
|
||||
} else {
|
||||
loadJson["assets"].emplace_back("ent://scripts/valve/trigger.lua");
|
||||
}
|
||||
// signal to assign a physics body
|
||||
if ( ext::json::isNull( node.metadata["physics"] ) ) {
|
||||
node.metadata["physics"]["type"] = "bounding box";
|
||||
|
||||
@ -39,7 +39,7 @@ void ext::vulkan::Buffer::aliasBuffer( const ext::vulkan::Buffer& buffer ) {
|
||||
this->memory = buffer.memory;
|
||||
this->descriptor = buffer.descriptor;
|
||||
this->alignment = buffer.alignment;
|
||||
this->stride = buffer.alignment;
|
||||
this->stride = buffer.stride;
|
||||
this->address = buffer.address;
|
||||
this->mapped = buffer.mapped;
|
||||
this->usage = buffer.usage;
|
||||
|
||||
@ -617,10 +617,14 @@ VkCommandBuffer ext::vulkan::Device::createCommandBuffer( VkCommandBufferLevel l
|
||||
if ( !pool.empty() ) {
|
||||
commandBuffer = pool.top();
|
||||
pool.pop();
|
||||
// pooled command buffers are returned in the recorded state, they must be reset before re-recording
|
||||
vkResetCommandBuffer( commandBuffer, 0 );
|
||||
}
|
||||
}
|
||||
if ( commandBuffer == VK_NULL_HANDLE ) {
|
||||
VkCommandBufferAllocateInfo cmdBufAllocateInfo = ext::vulkan::initializers::commandBufferAllocateInfo( getCommandPool(queue), level, 1 );
|
||||
auto pool = getCommandPool( queue );
|
||||
auto lock = this->lockPool( pool );
|
||||
VkCommandBufferAllocateInfo cmdBufAllocateInfo = ext::vulkan::initializers::commandBufferAllocateInfo( pool, level, 1 );
|
||||
VK_CHECK_RESULT( vkAllocateCommandBuffers( logicalDevice, &cmdBufAllocateInfo, &commandBuffer ) );
|
||||
}
|
||||
|
||||
@ -647,25 +651,28 @@ void ext::vulkan::Device::flushCommandBuffer( VkCommandBuffer commandBuffer, Que
|
||||
submitInfo.pCommandBuffers = &commandBuffer;
|
||||
|
||||
auto queue = getQueue( queueType );
|
||||
VK_CHECK_RESULT(vkQueueSubmit( queue, 1, &submitInfo, fence));
|
||||
{
|
||||
auto lock = this->lockQueue( queue );
|
||||
VK_CHECK_RESULT(vkQueueSubmit( queue, 1, &submitInfo, fence));
|
||||
|
||||
if ( immediate ) {
|
||||
VkResult res = vkWaitForFences( this->logicalDevice, 1, &fence, VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT );
|
||||
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
|
||||
if ( immediate ) {
|
||||
VkResult res = vkWaitForFences( this->logicalDevice, 1, &fence, VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT );
|
||||
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
|
||||
|
||||
uf::checkpoint::deallocate(checkpoints[commandBuffer]);
|
||||
checkpoints[commandBuffer] = NULL;
|
||||
checkpoints.erase(commandBuffer);
|
||||
uf::checkpoint::deallocate(checkpoints[commandBuffer]);
|
||||
checkpoints[commandBuffer] = NULL;
|
||||
checkpoints.erase(commandBuffer);
|
||||
|
||||
this->destroyFence( fence );
|
||||
this->destroyFence( fence );
|
||||
|
||||
this->reusable.commandBuffers[queueType][uf::thread::current_id()].emplace( commandBuffer );
|
||||
} else {
|
||||
ext::vulkan::mutex.lock();
|
||||
auto& transient = this->transient.commandBuffers[queueType][uf::thread::current_id()];
|
||||
transient.commandBuffers.emplace_back(commandBuffer);
|
||||
transient.fences.emplace_back(fence);
|
||||
ext::vulkan::mutex.unlock();
|
||||
this->reusable.commandBuffers[queueType][uf::thread::current_id()].emplace( commandBuffer );
|
||||
} else {
|
||||
ext::vulkan::mutex.lock();
|
||||
auto& transient = this->transient.commandBuffers[queueType][uf::thread::current_id()];
|
||||
transient.commandBuffers.emplace_back(commandBuffer);
|
||||
transient.fences.emplace_back(fence);
|
||||
ext::vulkan::mutex.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
pod::Checkpoint* ext::vulkan::Device::markCommandBuffer( VkCommandBuffer commandBuffer, pod::Checkpoint::Type type, const uf::stl::string& name, const uf::stl::string& info ) {
|
||||
@ -703,25 +710,28 @@ void ext::vulkan::Device::flushCommandBuffer( ext::vulkan::CommandBuffer& comman
|
||||
submitInfo.pCommandBuffers = &commandBuffer.handle;
|
||||
|
||||
auto queue = getQueue( commandBuffer.queueType, commandBuffer.threadId );
|
||||
VK_CHECK_RESULT(vkQueueSubmit( queue, 1, &submitInfo, fence));
|
||||
{
|
||||
auto lock = this->lockQueue( queue );
|
||||
VK_CHECK_RESULT(vkQueueSubmit( queue, 1, &submitInfo, fence));
|
||||
|
||||
if ( commandBuffer.immediate ) {
|
||||
VkResult res = vkWaitForFences( this->logicalDevice, 1, &fence, VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT );
|
||||
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
|
||||
if ( commandBuffer.immediate ) {
|
||||
VkResult res = vkWaitForFences( this->logicalDevice, 1, &fence, VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT );
|
||||
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
|
||||
|
||||
uf::checkpoint::deallocate(checkpoints[commandBuffer.handle]);
|
||||
checkpoints[commandBuffer.handle] = NULL;
|
||||
checkpoints.erase(commandBuffer.handle);
|
||||
uf::checkpoint::deallocate(checkpoints[commandBuffer.handle]);
|
||||
checkpoints[commandBuffer.handle] = NULL;
|
||||
checkpoints.erase(commandBuffer.handle);
|
||||
|
||||
this->destroyFence( fence );
|
||||
this->destroyFence( fence );
|
||||
|
||||
this->reusable.commandBuffers[commandBuffer.queueType][commandBuffer.threadId].emplace( commandBuffer.handle );
|
||||
} else {
|
||||
ext::vulkan::mutex.lock();
|
||||
auto& transient = this->transient.commandBuffers[commandBuffer.queueType][commandBuffer.threadId];
|
||||
transient.commandBuffers.emplace_back(commandBuffer.handle);
|
||||
transient.fences.emplace_back(fence);
|
||||
ext::vulkan::mutex.unlock();
|
||||
this->reusable.commandBuffers[commandBuffer.queueType][commandBuffer.threadId].emplace( commandBuffer.handle );
|
||||
} else {
|
||||
ext::vulkan::mutex.lock();
|
||||
auto& transient = this->transient.commandBuffers[commandBuffer.queueType][commandBuffer.threadId];
|
||||
transient.commandBuffers.emplace_back(commandBuffer.handle);
|
||||
transient.fences.emplace_back(fence);
|
||||
ext::vulkan::mutex.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -889,10 +899,28 @@ VkQueue ext::vulkan::Device::getQueue( ext::vulkan::QueueEnum queueEnum, uf::thr
|
||||
}
|
||||
return queue;
|
||||
}
|
||||
std::unique_lock<std::mutex> ext::vulkan::Device::lockQueue( VkQueue queue ) {
|
||||
UF_ASSERT( this->queueLocks );
|
||||
std::lock_guard<std::mutex> guard( this->queueLocks->mutex );
|
||||
auto& locks = this->queueLocks->locks;
|
||||
auto it = locks.find( queue );
|
||||
if ( it == locks.end() ) it = locks.emplace( queue, std::make_unique<std::mutex>() ).first;
|
||||
return std::unique_lock<std::mutex>( *it->second );
|
||||
}
|
||||
std::unique_lock<std::mutex> ext::vulkan::Device::lockPool( VkCommandPool pool ) {
|
||||
UF_ASSERT( this->queueLocks );
|
||||
std::lock_guard<std::mutex> guard( this->queueLocks->mutex );
|
||||
auto& locks = this->queueLocks->poolLocks;
|
||||
auto it = locks.find( pool );
|
||||
if ( it == locks.end() ) it = locks.emplace( pool, std::make_unique<std::mutex>() ).first;
|
||||
return std::unique_lock<std::mutex>( *it->second );
|
||||
}
|
||||
|
||||
void ext::vulkan::Device::initialize() {
|
||||
auto& device = *this;
|
||||
|
||||
this->queueLocks = std::make_unique<QueueLocks>();
|
||||
|
||||
uf::stl::vector<uf::stl::string> instanceLayers = {
|
||||
// "VK_LAYER_KHRONOS_synchronization2",
|
||||
};
|
||||
@ -1172,6 +1200,41 @@ void ext::vulkan::Device::initialize() {
|
||||
// Else we use the same queue
|
||||
queueFamilyIndices.transfer = queueFamilyIndices.graphics;
|
||||
}
|
||||
// Present queue (must be resolved before tallying, queues are created per-family)
|
||||
{
|
||||
uint32_t graphicsQueueNodeIndex = UINT32_MAX;
|
||||
uint32_t presentQueueNodeIndex = UINT32_MAX;
|
||||
uint32_t computeQueueNodeIndex = UINT32_MAX;
|
||||
uint32_t transferQueueNodeIndex = UINT32_MAX;
|
||||
|
||||
int i = 0;
|
||||
for (const auto& queueFamily : queueFamilyProperties) {
|
||||
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT ) {
|
||||
graphicsQueueNodeIndex = i;
|
||||
}
|
||||
|
||||
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT ) {
|
||||
computeQueueNodeIndex = i;
|
||||
}
|
||||
|
||||
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_TRANSFER_BIT ) {
|
||||
transferQueueNodeIndex = i;
|
||||
}
|
||||
|
||||
VkBool32 presentSupport = false;
|
||||
vkGetPhysicalDeviceSurfaceSupportKHR( this->physicalDevice, i, surface, &presentSupport );
|
||||
if ( queueFamily.queueCount > 0 && presentSupport ) {
|
||||
presentQueueNodeIndex = i;
|
||||
}
|
||||
|
||||
if ( graphicsQueueNodeIndex != UINT32_MAX && presentQueueNodeIndex != UINT32_MAX && computeQueueNodeIndex != UINT32_MAX ) break;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if ( presentQueueNodeIndex == UINT32_MAX ) presentQueueNodeIndex = queueFamilyIndices.graphics;
|
||||
queueFamilyIndices.present = presentQueueNodeIndex;
|
||||
}
|
||||
// Dedicated acquire queue
|
||||
{
|
||||
queueFamilyIndices.acquire = queueFamilyIndices.present;
|
||||
@ -1203,7 +1266,12 @@ void ext::vulkan::Device::initialize() {
|
||||
{
|
||||
std::map<uint32_t, uint32_t> familyIndexCounters;
|
||||
auto assignQueueIndex = [&](uint32_t family) -> uint32_t {
|
||||
return familyIndexCounters[family]++;
|
||||
// roles may share a queue when the family has fewer queues than requested
|
||||
uint32_t available = MAX( 1, std::min( requestedQueuesPerFamily[family], queueFamilyProperties[family].queueCount ) );
|
||||
auto& counter = familyIndexCounters[family];
|
||||
uint32_t index = counter % available;
|
||||
counter++;
|
||||
return index;
|
||||
};
|
||||
|
||||
device.queueIndices.graphics = assignQueueIndex( device.queueFamilyIndices.graphics );
|
||||
@ -1227,8 +1295,7 @@ void ext::vulkan::Device::initialize() {
|
||||
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||
deviceCreateInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());;
|
||||
deviceCreateInfo.pQueueCreateInfos = queueCreateInfos.data();
|
||||
// deviceCreateInfo.pEnabledFeatures = &enabledFeatures;
|
||||
deviceCreateInfo.pEnabledFeatures = nullptr;
|
||||
deviceCreateInfo.pEnabledFeatures = ext::vulkan::settings::requested::featureChain["physicalDevice2"].as<bool>(false) ? nullptr : &enabledFeatures;
|
||||
|
||||
VkDeviceGroupDeviceCreateInfo groupDeviceCreateInfo = {};
|
||||
groupDeviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_GROUP_DEVICE_CREATE_INFO;
|
||||
@ -1409,43 +1476,12 @@ void ext::vulkan::Device::initialize() {
|
||||
getCommandPool( QueueEnum::TRANSFER );
|
||||
// Set queue
|
||||
{
|
||||
uint32_t graphicsQueueNodeIndex = UINT32_MAX;
|
||||
uint32_t presentQueueNodeIndex = UINT32_MAX;
|
||||
uint32_t computeQueueNodeIndex = UINT32_MAX;
|
||||
uint32_t transferQueueNodeIndex = UINT32_MAX;
|
||||
|
||||
int i = 0;
|
||||
for (const auto& queueFamily : queueFamilyProperties) {
|
||||
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT ) {
|
||||
graphicsQueueNodeIndex = i;
|
||||
}
|
||||
|
||||
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT ) {
|
||||
computeQueueNodeIndex = i;
|
||||
}
|
||||
|
||||
if ( queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_TRANSFER_BIT ) {
|
||||
transferQueueNodeIndex = i;
|
||||
}
|
||||
|
||||
VkBool32 presentSupport = false;
|
||||
vkGetPhysicalDeviceSurfaceSupportKHR( this->physicalDevice, i, surface, &presentSupport );
|
||||
if ( queueFamily.queueCount > 0 && presentSupport ) {
|
||||
presentQueueNodeIndex = i;
|
||||
}
|
||||
|
||||
if ( graphicsQueueNodeIndex != UINT32_MAX && presentQueueNodeIndex != UINT32_MAX && computeQueueNodeIndex != UINT32_MAX ) break;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
VK_VALIDATION_MESSAGE("Graphics queue: family={}, index={}", device.queueFamilyIndices.graphics, device.queueIndices.graphics );
|
||||
VK_VALIDATION_MESSAGE("Compute queue: family={}, index={}", device.queueFamilyIndices.compute, device.queueIndices.compute );
|
||||
VK_VALIDATION_MESSAGE("Transfer queue: family={}, index={}", device.queueFamilyIndices.transfer, device.queueIndices.transfer );
|
||||
VK_VALIDATION_MESSAGE("Present queue: family={}, index={}", device.queueFamilyIndices.present, device.queueIndices.present );
|
||||
VK_VALIDATION_MESSAGE("Acquire queue: family={}, index={}", device.queueFamilyIndices.acquire, device.queueIndices.acquire );
|
||||
|
||||
device.queueFamilyIndices.present = presentQueueNodeIndex;
|
||||
getQueue( QueueEnum::GRAPHICS );
|
||||
getQueue( QueueEnum::PRESENT );
|
||||
getQueue( QueueEnum::COMPUTE );
|
||||
@ -1613,7 +1649,7 @@ void ext::vulkan::Device::destroy() {
|
||||
for ( auto& pair_1 : this->transient.commandBuffers ) {
|
||||
for ( auto& pair : pair_1.second ) {
|
||||
for ( auto& commandBuffer : pair.second.commandBuffers ) {
|
||||
vkFreeCommandBuffers(logicalDevice, getCommandPool( pair_1.first ), 1, &commandBuffer);
|
||||
vkFreeCommandBuffers(logicalDevice, getCommandPool( pair_1.first, pair.first ), 1, &commandBuffer);
|
||||
VK_UNREGISTER_HANDLE( commandBuffer );
|
||||
commandBuffer = VK_NULL_HANDLE;
|
||||
}
|
||||
@ -1635,8 +1671,8 @@ void ext::vulkan::Device::destroy() {
|
||||
VK_UNREGISTER_HANDLE( fence );
|
||||
}
|
||||
for ( auto& [queueType, threadMap] : this->reusable.commandBuffers) {
|
||||
VkCommandPool commandPool = getCommandPool(queueType);
|
||||
for ( auto& [threadId, pool] : threadMap) {
|
||||
VkCommandPool commandPool = getCommandPool(queueType, threadId);
|
||||
if ( pool.empty() ) continue;
|
||||
uf::stl::vector<VkCommandBuffer> buffersToFree;
|
||||
buffersToFree.reserve(pool.size());
|
||||
@ -1670,6 +1706,18 @@ void ext::vulkan::Device::destroy() {
|
||||
for ( auto& pair : Pipeline::pipelines ) pair.second.destroy();
|
||||
Pipeline::pipelines.clear();
|
||||
|
||||
// pipelines may have deferred their SBT buffers into transient.buffers above
|
||||
for ( auto& buffer : this->transient.buffers ) {
|
||||
buffer.destroy(false);
|
||||
}
|
||||
this->transient.buffers.clear();
|
||||
|
||||
// deferred texture destroys (e.g. the empty textures) that outlived the last flush
|
||||
for ( auto& texture : this->transient.textures ) {
|
||||
texture.destroy(false);
|
||||
}
|
||||
this->transient.textures.clear();
|
||||
|
||||
descriptorAllocator.destroy();
|
||||
|
||||
for ( auto& pair : this->commandPool.graphics.container() ) {
|
||||
@ -1687,6 +1735,20 @@ void ext::vulkan::Device::destroy() {
|
||||
VK_UNREGISTER_HANDLE( pair.second );
|
||||
pair.second = VK_NULL_HANDLE;
|
||||
}
|
||||
// the VMA allocator must die while the device is still alive, and only once every allocation it owns has been freed
|
||||
if ( allocator ) {
|
||||
VmaTotalStatistics statistics = {};
|
||||
vmaCalculateStatistics( allocator, &statistics );
|
||||
uint32_t allocationCount = 0;
|
||||
for ( auto& detail : statistics.memoryType ) allocationCount += detail.statistics.allocationCount;
|
||||
if ( allocationCount > 0 ) {
|
||||
UF_MSG_DEBUG("VMA allocator still owns {} allocations at shutdown", allocationCount );
|
||||
}
|
||||
vmaDestroyAllocator( allocator );
|
||||
VK_UNREGISTER_HANDLE( allocator );
|
||||
allocator = nullptr;
|
||||
}
|
||||
|
||||
if ( this->logicalDevice ) {
|
||||
vkDestroyDevice( this->logicalDevice, nullptr );
|
||||
VK_UNREGISTER_HANDLE( this->logicalDevice );
|
||||
@ -1706,9 +1768,6 @@ void ext::vulkan::Device::destroy() {
|
||||
VK_UNREGISTER_HANDLE( this->instance );
|
||||
this->instance = nullptr;
|
||||
}
|
||||
|
||||
// vmaDestroyAllocator( allocator );
|
||||
VK_UNREGISTER_HANDLE( allocator );
|
||||
}
|
||||
|
||||
void ext::vulkan::DescriptorAllocator::initialize(VkDevice inDevice) {
|
||||
|
||||
@ -487,16 +487,16 @@ void ext::vulkan::Pipeline::destroy() {
|
||||
descriptorPool = VK_NULL_HANDLE;
|
||||
}
|
||||
*/
|
||||
if ( pipelineLayout != VK_NULL_HANDLE ) {
|
||||
vkDestroyPipelineLayout( *device, pipelineLayout, nullptr );
|
||||
VK_UNREGISTER_HANDLE( pipelineLayout );
|
||||
pipelineLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
if ( pipeline != VK_NULL_HANDLE ) {
|
||||
vkDestroyPipeline( *device, pipeline, nullptr );
|
||||
VK_UNREGISTER_HANDLE( pipeline );
|
||||
pipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
if ( pipelineLayout != VK_NULL_HANDLE ) {
|
||||
vkDestroyPipelineLayout( *device, pipelineLayout, nullptr );
|
||||
VK_UNREGISTER_HANDLE( pipelineLayout );
|
||||
pipelineLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
for ( auto descriptorSetLayout : descriptorSetLayouts ) {
|
||||
if ( descriptorSetLayout != VK_NULL_HANDLE ) {
|
||||
vkDestroyDescriptorSetLayout( *device, descriptorSetLayout, nullptr );
|
||||
@ -505,6 +505,8 @@ void ext::vulkan::Pipeline::destroy() {
|
||||
}
|
||||
descriptorSetLayouts.clear();
|
||||
|
||||
ext::vulkan::Buffers::destroy();
|
||||
|
||||
// if ( settings::experimental::dedicatedThread ) ext::vulkan::states::rebuild = true;
|
||||
/*
|
||||
if ( ext::vulkan::hasRenderMode(descriptor.renderMode, true) ) {
|
||||
@ -1004,6 +1006,8 @@ void ext::vulkan::DescriptorSets::update( const Graphic& graphic, const GraphicD
|
||||
} else */ {
|
||||
vkUpdateDescriptorSets( *device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL );
|
||||
}
|
||||
// the updated sets may be bound into standing command buffers; re-record them so they pick up the new contents
|
||||
renderMode.rerecord = true;
|
||||
this->metadata.built = true;
|
||||
return;
|
||||
|
||||
@ -1714,6 +1718,10 @@ void ext::vulkan::Graphic::generateTopAccelerationStructure( const uf::stl::vect
|
||||
} else UF_EXCEPTION("Buffers not found: {}", "tlasInstance");
|
||||
|
||||
auto& buffer = this->buffers.at(instanceIndex);
|
||||
if ( instancesVK.size() * sizeof( VkAccelerationStructureInstanceKHR ) > buffer.allocationInfo.size ) {
|
||||
buffer.destroy();
|
||||
buffer.initialize( NULL, instancesVK.size() * sizeof( VkAccelerationStructureInstanceKHR ), VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR );
|
||||
}
|
||||
void* map = buffer.map();
|
||||
uf::stl::memcpy(map, instancesVK.data(), instancesVK.size() * sizeof(VkAccelerationStructureInstanceKHR));
|
||||
buffer.unmap();
|
||||
@ -1942,7 +1950,7 @@ void ext::vulkan::Graphic::initializeDescriptorSet() {
|
||||
initializeDescriptorSet( this->descriptor );
|
||||
}
|
||||
ext::vulkan::DescriptorSets& ext::vulkan::Graphic::initializeDescriptorSet( const GraphicDescriptor& descriptor ) {
|
||||
auto& pipeline = getPipeline();
|
||||
auto& pipeline = getPipeline( descriptor );
|
||||
auto& descriptorSet = descriptorSets[descriptor];
|
||||
|
||||
// ensure pipeline exists (because we're passing this as const)
|
||||
|
||||
@ -267,13 +267,17 @@ ext::vulkan::RenderMode::commands_container_t& ext::vulkan::RenderMode::getComma
|
||||
if ( !exists ) {
|
||||
commands.resize( swapchain.buffers );
|
||||
|
||||
VkCommandBufferAllocateInfo cmdBufAllocateInfo = ext::vulkan::initializers::commandBufferAllocateInfo(
|
||||
device->getCommandPool(this->queueEnum, id),
|
||||
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
static_cast<uint32_t>(commands.size())
|
||||
);
|
||||
auto pool = device->getCommandPool(this->queueEnum, id);
|
||||
{
|
||||
auto lock = device->lockPool( pool );
|
||||
VkCommandBufferAllocateInfo cmdBufAllocateInfo = ext::vulkan::initializers::commandBufferAllocateInfo(
|
||||
pool,
|
||||
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
static_cast<uint32_t>(commands.size())
|
||||
);
|
||||
|
||||
VK_CHECK_RESULT(vkAllocateCommandBuffers(*device, &cmdBufAllocateInfo, commands.data()));
|
||||
VK_CHECK_RESULT(vkAllocateCommandBuffers(*device, &cmdBufAllocateInfo, commands.data()));
|
||||
}
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
@ -295,7 +299,10 @@ void ext::vulkan::RenderMode::cleanupAllCommands() {
|
||||
if ( commandBuffers.empty() ) continue;
|
||||
|
||||
VkQueue queue = device->getQueue( queueEnum, threadID );
|
||||
vkQueueWaitIdle( queue );
|
||||
{
|
||||
auto lock = device->lockQueue( queue );
|
||||
vkQueueWaitIdle( queue );
|
||||
}
|
||||
/*
|
||||
VkResult res = vkWaitForFences( *device, fences.size(), fences.data(), VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT );
|
||||
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
|
||||
@ -307,7 +314,11 @@ void ext::vulkan::RenderMode::cleanupAllCommands() {
|
||||
device->checkpoints.erase(commandBuffer);
|
||||
}
|
||||
|
||||
vkFreeCommandBuffers( *device, device->getCommandPool(queueEnum, threadID), static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
|
||||
{
|
||||
auto pool = device->getCommandPool(queueEnum, threadID);
|
||||
auto lock = device->lockPool( pool );
|
||||
vkFreeCommandBuffers( *device, pool, static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
|
||||
}
|
||||
commandBuffers.clear();
|
||||
}
|
||||
container.clear();
|
||||
@ -320,7 +331,10 @@ void ext::vulkan::RenderMode::cleanupCommands( uf::thread::id_t id ) {
|
||||
|
||||
|
||||
VkQueue queue = device->getQueue( queueEnum, threadID );
|
||||
vkQueueWaitIdle( queue );
|
||||
{
|
||||
auto lock = device->lockQueue( queue );
|
||||
vkQueueWaitIdle( queue );
|
||||
}
|
||||
/*
|
||||
VkResult res = vkWaitForFences( *device, fences.size(), fences.data(), VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT );
|
||||
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
|
||||
@ -332,7 +346,11 @@ void ext::vulkan::RenderMode::cleanupCommands( uf::thread::id_t id ) {
|
||||
device->checkpoints.erase(commandBuffer);
|
||||
}
|
||||
|
||||
vkFreeCommandBuffers( *device, device->getCommandPool(queueEnum, threadID), static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
|
||||
{
|
||||
auto pool = device->getCommandPool(queueEnum, threadID);
|
||||
auto lock = device->lockPool( pool );
|
||||
vkFreeCommandBuffers( *device, pool, static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data());
|
||||
}
|
||||
commandBuffers.clear();
|
||||
}
|
||||
this->commands.cleanup( id );
|
||||
@ -481,7 +499,9 @@ void ext::vulkan::RenderMode::destroy() {
|
||||
|
||||
for ( auto& pair : this->commands.container() ) {
|
||||
if ( !pair.second.empty() ) {
|
||||
vkFreeCommandBuffers( *device, device->getCommandPool(this->queueEnum, pair.first), static_cast<uint32_t>(pair.second.size()), pair.second.data());
|
||||
auto pool = device->getCommandPool(this->queueEnum, pair.first);
|
||||
auto lock = device->lockPool( pool );
|
||||
vkFreeCommandBuffers( *device, pool, static_cast<uint32_t>(pair.second.size()), pair.second.data());
|
||||
}
|
||||
pair.second.clear();
|
||||
}
|
||||
@ -506,9 +526,12 @@ void ext::vulkan::RenderMode::synchronize( uint64_t timeout ) {
|
||||
lockMutex();
|
||||
|
||||
VkQueue queue = device->getQueue( queueEnum, this->mostRecentCommandPoolId );
|
||||
VkResult res = vkWaitForFences( *device, fences.size(), fences.data(), VK_TRUE, timeout );
|
||||
// VkResult res = vkWaitForFences(*device, 1, &fences[states::currentBuffer], VK_TRUE, timeout);
|
||||
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
|
||||
{
|
||||
auto lock = device->lockQueue( queue );
|
||||
VkResult res = vkWaitForFences( *device, fences.size(), fences.data(), VK_TRUE, timeout );
|
||||
// VkResult res = vkWaitForFences(*device, 1, &fences[states::currentBuffer], VK_TRUE, timeout);
|
||||
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
|
||||
}
|
||||
|
||||
unlockMutex();
|
||||
}
|
||||
|
||||
@ -111,7 +111,8 @@ VkSubmitInfo ext::vulkan::BaseRenderMode::queue() {
|
||||
submitInfo.pWaitDstStageMask = waitStageMask;
|
||||
submitInfo.pWaitSemaphores = &swapchain.presentCompleteSemaphores[states::currentBuffer];
|
||||
submitInfo.waitSemaphoreCount = 1;
|
||||
submitInfo.pSignalSemaphores = &renderCompleteSemaphores[states::currentBuffer];
|
||||
// the present-wait semaphore is paired with the acquired image, so it is only reused once that image is re-acquired
|
||||
submitInfo.pSignalSemaphores = &renderCompleteSemaphores[states::imageIndex];
|
||||
submitInfo.signalSemaphoreCount = 1;
|
||||
submitInfo.pCommandBuffers = &commands[states::currentBuffer];
|
||||
submitInfo.commandBufferCount = 1;
|
||||
@ -129,11 +130,16 @@ void ext::vulkan::BaseRenderMode::render() {
|
||||
{
|
||||
VkSubmitInfo submitInfo = this->queue();
|
||||
VkQueue queue = device->getQueue( QueueEnum::GRAPHICS );
|
||||
auto lock = device->lockQueue( queue );
|
||||
VkResult res = vkQueueSubmit( queue, 1, &submitInfo, fences[states::currentBuffer]);
|
||||
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
|
||||
}
|
||||
|
||||
VK_CHECK_RESULT(swapchain.queuePresent(device->getQueue( QueueEnum::PRESENT ), states::imageIndex, renderCompleteSemaphores[states::currentBuffer]));
|
||||
{
|
||||
VkQueue queue = device->getQueue( QueueEnum::PRESENT );
|
||||
auto lock = device->lockQueue( queue );
|
||||
VK_CHECK_RESULT(swapchain.queuePresent( queue, states::imageIndex, renderCompleteSemaphores[states::imageIndex]));
|
||||
}
|
||||
|
||||
states::currentBuffer = (states::currentBuffer + 1) % ext::vulkan::swapchain.buffers;
|
||||
this->executed = true;
|
||||
|
||||
@ -668,10 +668,17 @@ void ext::vulkan::DeferredRenderMode::render() {
|
||||
|
||||
VK_COMMAND_BUFFER_CALLBACK( EXECUTE_BEGIN, VkCommandBuffer{}, 0, {} );
|
||||
|
||||
// wait on the slot's previous deferred submit so its fence can be re-signaled
|
||||
VK_CHECK_RESULT(vkWaitForFences( *device, 1, &fences[states::currentBuffer], VK_TRUE, VK_DEFAULT_FENCE_TIMEOUT ));
|
||||
VK_CHECK_RESULT(vkResetFences( *device, 1, &fences[states::currentBuffer] ));
|
||||
|
||||
VkSubmitInfo submitInfo = this->queue();
|
||||
VkQueue queue = device->getQueue( QueueEnum::GRAPHICS );
|
||||
VkResult res = vkQueueSubmit( queue, 1, &submitInfo, VK_NULL_HANDLE/*fences[states::currentBuffer]*/);
|
||||
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
|
||||
{
|
||||
VkQueue queue = device->getQueue( QueueEnum::GRAPHICS );
|
||||
auto lock = device->lockQueue( queue );
|
||||
VkResult res = vkQueueSubmit( queue, 1, &submitInfo, fences[states::currentBuffer]);
|
||||
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
|
||||
}
|
||||
|
||||
VK_COMMAND_BUFFER_CALLBACK( EXECUTE_END, VkCommandBuffer{}, 0, {} );
|
||||
|
||||
@ -686,18 +693,21 @@ void ext::vulkan::DeferredRenderMode::destroy() {
|
||||
::postprocesses::dof.atomicCounter.destroy(false);
|
||||
|
||||
for ( auto& view : ::postprocesses::bloom.views ) {
|
||||
|
||||
vkDestroyImageView(device->logicalDevice, view, nullptr);
|
||||
VK_UNREGISTER_HANDLE(view);
|
||||
}
|
||||
::postprocesses::bloom.views.clear();
|
||||
|
||||
for ( auto& view : ::postprocesses::dof.views ) {
|
||||
|
||||
vkDestroyImageView(device->logicalDevice, view, nullptr);
|
||||
VK_UNREGISTER_HANDLE(view);
|
||||
}
|
||||
::postprocesses::dof.views.clear();
|
||||
|
||||
for ( auto& view : ::postprocesses::depthPyramid.views ) {
|
||||
|
||||
vkDestroyImageView(device->logicalDevice, view, nullptr);
|
||||
VK_UNREGISTER_HANDLE(view);
|
||||
}
|
||||
|
||||
@ -333,8 +333,11 @@ void ext::vulkan::RenderTargetRenderMode::render() {
|
||||
|
||||
VkSubmitInfo submitInfo = this->queue();
|
||||
VkQueue queue = device->getQueue( QueueEnum::GRAPHICS );
|
||||
VkResult res = vkQueueSubmit( queue, 1, &submitInfo, /*VK_NULL_HANDLE*/fences[states::currentBuffer]);
|
||||
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
|
||||
{
|
||||
auto lock = device->lockQueue( queue );
|
||||
VkResult res = vkQueueSubmit( queue, 1, &submitInfo, /*VK_NULL_HANDLE*/fences[states::currentBuffer]);
|
||||
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
|
||||
}
|
||||
VK_COMMAND_BUFFER_CALLBACK( EXECUTE_END, VkCommandBuffer{}, 0, {} );
|
||||
|
||||
this->executed = true;
|
||||
|
||||
@ -251,8 +251,11 @@ void ext::vulkan::VrRenderMode::render() {
|
||||
VkSubmitInfo submitInfo = this->queue();
|
||||
|
||||
VkQueue queue = device->getQueue( QueueEnum::GRAPHICS );
|
||||
VkResult res = vkQueueSubmit( queue, 1, &submitInfo, /*VK_NULL_HANDLE*/fences[states::currentBuffer]);
|
||||
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
|
||||
{
|
||||
auto lock = device->lockQueue( queue );
|
||||
VkResult res = vkQueueSubmit( queue, 1, &submitInfo, /*VK_NULL_HANDLE*/fences[states::currentBuffer]);
|
||||
VK_CHECK_QUEUE_CHECKPOINT( queue, res );
|
||||
}
|
||||
VK_COMMAND_BUFFER_CALLBACK( EXECUTE_END, VkCommandBuffer{}, 0, {} );
|
||||
#if UF_USE_OPENVR
|
||||
ext::openvr::submit();
|
||||
|
||||
@ -35,6 +35,17 @@ size_t ext::vulkan::RenderTarget::attach( const Attachment::Descriptor& descript
|
||||
VK_UNREGISTER_HANDLE( view );
|
||||
}
|
||||
attachment->views.clear();
|
||||
// view and framebufferView are distinct handles not covered by views[]
|
||||
if ( attachment->view ) {
|
||||
vkDestroyImageView( *device, attachment->view, nullptr );
|
||||
VK_UNREGISTER_HANDLE( attachment->view );
|
||||
attachment->view = VK_NULL_HANDLE;
|
||||
}
|
||||
if ( attachment->framebufferView ) {
|
||||
vkDestroyImageView( *device, attachment->framebufferView, nullptr );
|
||||
VK_UNREGISTER_HANDLE( attachment->framebufferView );
|
||||
attachment->framebufferView = VK_NULL_HANDLE;
|
||||
}
|
||||
if ( attachment->image && attachment->descriptor.layout != VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ) {
|
||||
vmaDestroyImage( allocator, attachment->image, attachment->allocation );
|
||||
attachment->image = VK_NULL_HANDLE;
|
||||
@ -482,7 +493,17 @@ void ext::vulkan::RenderTarget::destroy() {
|
||||
framebuffers.clear();
|
||||
|
||||
for ( auto& attachment : attachments ) {
|
||||
if ( attachment.descriptor.aliased ) continue;
|
||||
if ( attachment.descriptor.aliased ) {
|
||||
if ( attachment.descriptor.layout != VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ) continue;
|
||||
for ( auto& view : attachment.views ) {
|
||||
if ( view != VK_NULL_HANDLE ) {
|
||||
vkDestroyImageView( *device, view, nullptr );
|
||||
VK_UNREGISTER_HANDLE( view );
|
||||
}
|
||||
}
|
||||
attachment.views.clear();
|
||||
continue;
|
||||
}
|
||||
if ( attachment.framebufferView ) {
|
||||
vkDestroyImageView(*device, attachment.framebufferView, nullptr);
|
||||
VK_UNREGISTER_HANDLE( attachment.framebufferView );
|
||||
|
||||
@ -696,6 +696,7 @@ void ext::vulkan::Texture::asRenderTarget( Device& device, uint32_t width, uint3
|
||||
viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
|
||||
viewCreateInfo.image = image;
|
||||
VK_CHECK_RESULT(vkCreateImageView(device, &viewCreateInfo, nullptr, &view));
|
||||
VK_REGISTER_HANDLE( view );
|
||||
|
||||
// Initialize a descriptor for later use
|
||||
this->updateDescriptors();
|
||||
|
||||
@ -540,10 +540,14 @@ void ext::vulkan::tick() {
|
||||
auto tasks = uf::thread::schedule( settings::invariant::multithreadedRecording );
|
||||
for ( auto& renderMode : renderModes ) { if ( !renderMode || (renderMode->executed && !renderMode->execute) ) continue;
|
||||
if ( ext::vulkan::states::rebuild || renderMode->rebuild ) tasks.queue([renderMode]{
|
||||
renderMode->synchronize();
|
||||
renderMode->cleanupAllCommands();
|
||||
renderMode->bindPipelines();
|
||||
renderMode->createCommandBuffers();
|
||||
});
|
||||
else if ( renderMode->rerecord ) tasks.queue([renderMode]{
|
||||
renderMode->synchronize();
|
||||
renderMode->cleanupAllCommands();
|
||||
renderMode->createCommandBuffers();
|
||||
});
|
||||
}
|
||||
@ -641,9 +645,17 @@ void ext::vulkan::render() {
|
||||
VK_CHECK_RESULT(vkResetFences(device, fences.size(), fences.data()));
|
||||
}
|
||||
if ( !submitsCompute.empty() )
|
||||
VK_CHECK_RESULT(vkQueueSubmit(device.getQueue( QueueEnum::COMPUTE ), submitsCompute.size(), submitsCompute.data(), ::auxFences.compute[states::currentBuffer]));
|
||||
{
|
||||
VkQueue queue = device.getQueue( QueueEnum::COMPUTE );
|
||||
auto lock = device.lockQueue( queue );
|
||||
VK_CHECK_RESULT(vkQueueSubmit(queue, submitsCompute.size(), submitsCompute.data(), ::auxFences.compute[states::currentBuffer]));
|
||||
}
|
||||
if ( !submitsGraphics.empty() )
|
||||
VK_CHECK_RESULT(vkQueueSubmit(device.getQueue( QueueEnum::GRAPHICS ), submitsGraphics.size(), submitsGraphics.data(), ::auxFences.graphics[states::currentBuffer]));
|
||||
{
|
||||
VkQueue queue = device.getQueue( QueueEnum::GRAPHICS );
|
||||
auto lock = device.lockQueue( queue );
|
||||
VK_CHECK_RESULT(vkQueueSubmit(queue, submitsGraphics.size(), submitsGraphics.data(), ::auxFences.graphics[states::currentBuffer]));
|
||||
}
|
||||
}
|
||||
|
||||
// submit swapchain (record + submit + present) last
|
||||
@ -696,6 +708,28 @@ void ext::vulkan::render() {
|
||||
|
||||
// ext::vulkan::mutex.unlock();
|
||||
|
||||
// wait on any in-flight transient commands so their staging resources can be destroyed, and return their command buffers to the reusable pool
|
||||
for ( auto& [ queueType, commandBuffers ] : transient.commandBuffers ) {
|
||||
for ( auto& [ threadId, tuple ] : commandBuffers ) {
|
||||
constexpr size_t TOTAL = 64;
|
||||
for ( size_t i = 0; i < tuple.fences.size(); i += TOTAL ) {
|
||||
size_t count = std::min( tuple.fences.size() - i, TOTAL );
|
||||
VK_CHECK_RESULT( vkWaitForFences( device, count, &tuple.fences[i], VK_TRUE, UINT64_MAX ) );
|
||||
}
|
||||
for ( auto fence : tuple.fences ) device.destroyFence( fence );
|
||||
tuple.fences.clear();
|
||||
auto& pool = device.reusable.commandBuffers[queueType][threadId];
|
||||
for ( auto commandBuffer : tuple.commandBuffers ) {
|
||||
if ( auto it = device.checkpoints.find(commandBuffer); it != device.checkpoints.end() ) {
|
||||
uf::checkpoint::deallocate( it->second );
|
||||
device.checkpoints.erase( it );
|
||||
}
|
||||
pool.emplace( commandBuffer );
|
||||
}
|
||||
tuple.commandBuffers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup in-flight buffers
|
||||
for ( auto& buffer : transient.buffers ) buffer.destroy(false);
|
||||
transient.buffers.clear();
|
||||
@ -726,8 +760,9 @@ void ext::vulkan::destroy( bool soft ) {
|
||||
for ( auto& renderMode : renderModes ) {
|
||||
if ( !renderMode || !renderMode->device ) continue;
|
||||
renderMode->destroy();
|
||||
delete renderMode;
|
||||
renderMode = NULL;
|
||||
if ( std::find( ext::vulkan::renderModes.begin(), ext::vulkan::renderModes.end(), renderMode ) != ext::vulkan::renderModes.end() ) {
|
||||
delete renderMode;
|
||||
}
|
||||
}
|
||||
|
||||
ext::vulkan::renderModes.clear();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user