mirror of
https://github.com/libsdl-org/SDL.git
synced 2025-09-16 00:08:14 +00:00

It was intended to make the API easier to use, but various automatic garbage collection all had flaws, and making the application periodically clean up temporary memory added cognitive load to using the API, and in many cases was it was difficult to restructure threaded code to handle this. So, we're largely going back to the original system, where the API returns allocated results and you free them. In addition, to solve the problems we originally wanted temporary memory for: * Short strings with a finite count, like device names, get stored in a per-thread string pool. * Events continue to use temporary memory internally, which is cleaned up on the next event processing cycle.
585 lines
19 KiB
Python
Executable File
585 lines
19 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# Simple DirectMedia Layer
|
|
# Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
|
#
|
|
# This software is provided 'as-is', without any express or implied
|
|
# warranty. In no event will the authors be held liable for any damages
|
|
# arising from the use of this software.
|
|
#
|
|
# Permission is granted to anyone to use this software for any purpose,
|
|
# including commercial applications, and to alter it and redistribute it
|
|
# freely, subject to the following restrictions:
|
|
#
|
|
# 1. The origin of this software must not be misrepresented; you must not
|
|
# claim that you wrote the original software. If you use this software
|
|
# in a product, an acknowledgment in the product documentation would be
|
|
# appreciated but is not required.
|
|
# 2. Altered source versions must be plainly marked as such, and must not be
|
|
# misrepresented as being the original software.
|
|
# 3. This notice may not be removed or altered from any source distribution.
|
|
|
|
# WHAT IS THIS?
|
|
# When you add a public API to SDL, please run this script, make sure the
|
|
# output looks sane (git diff, it adds to existing files), and commit it.
|
|
# It keeps the dynamic API jump table operating correctly.
|
|
#
|
|
# OS-specific API:
|
|
# After running the script, you have to manually add #ifdef SDL_PLATFORM_WIN32
|
|
# or similar around the function in 'SDL_dynapi_procs.h'
|
|
#
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import pathlib
|
|
import pprint
|
|
import re
|
|
|
|
|
|
SDL_ROOT = pathlib.Path(__file__).resolve().parents[2]
|
|
|
|
SDL_INCLUDE_DIR = SDL_ROOT / "include/SDL3"
|
|
SDL_DYNAPI_PROCS_H = SDL_ROOT / "src/dynapi/SDL_dynapi_procs.h"
|
|
SDL_DYNAPI_OVERRIDES_H = SDL_ROOT / "src/dynapi/SDL_dynapi_overrides.h"
|
|
SDL_DYNAPI_SYM = SDL_ROOT / "src/dynapi/SDL_dynapi.sym"
|
|
|
|
full_API = []
|
|
|
|
|
|
def main():
|
|
|
|
# Parse 'sdl_dynapi_procs_h' file to find existing functions
|
|
existing_procs = find_existing_procs()
|
|
|
|
# Get list of SDL headers
|
|
sdl_list_includes = get_header_list()
|
|
|
|
reg_externC = re.compile(r'.*extern[ "]*C[ "].*')
|
|
reg_comment_remove_content = re.compile(r'\/\*.*\*/')
|
|
reg_parsing_function = re.compile(r'(.*SDLCALL[^\(\)]*) ([a-zA-Z0-9_]+) *\((.*)\) *;.*')
|
|
|
|
#eg:
|
|
# void (SDLCALL *callback)(void*, int)
|
|
# \1(\2)\3
|
|
reg_parsing_callback = re.compile(r'([^\(\)]*)\(([^\(\)]+)\)(.*)')
|
|
|
|
for filename in sdl_list_includes:
|
|
if args.debug:
|
|
print("Parse header: %s" % filename)
|
|
|
|
input = open(filename)
|
|
|
|
parsing_function = False
|
|
current_func = ""
|
|
parsing_comment = False
|
|
current_comment = ""
|
|
|
|
ignore_wiki_documentation = False
|
|
|
|
for line in input:
|
|
|
|
# Skip lines if we're in a wiki documentation block.
|
|
if ignore_wiki_documentation:
|
|
if line.startswith("#endif"):
|
|
ignore_wiki_documentation = False
|
|
continue
|
|
|
|
# Discard wiki documentions blocks.
|
|
if line.startswith("#ifdef SDL_WIKI_DOCUMENTATION_SECTION"):
|
|
ignore_wiki_documentation = True
|
|
continue
|
|
|
|
# Discard pre-processor directives ^#.*
|
|
if line.startswith("#"):
|
|
continue
|
|
|
|
# Discard "extern C" line
|
|
match = reg_externC.match(line)
|
|
if match:
|
|
continue
|
|
|
|
# Remove one line comment /* ... */
|
|
# eg: extern SDL_DECLSPEC SDL_hid_device * SDLCALL SDL_hid_open_path(const char *path, int bExclusive /* = false */);
|
|
line = reg_comment_remove_content.sub('', line)
|
|
|
|
# Get the comment block /* ... */ across several lines
|
|
match_start = "/*" in line
|
|
match_end = "*/" in line
|
|
if match_start and match_end:
|
|
continue
|
|
if match_start:
|
|
parsing_comment = True
|
|
current_comment = line
|
|
continue
|
|
if match_end:
|
|
parsing_comment = False
|
|
current_comment += line
|
|
continue
|
|
if parsing_comment:
|
|
current_comment += line
|
|
continue
|
|
|
|
# Get the function prototype across several lines
|
|
if parsing_function:
|
|
# Append to the current function
|
|
current_func += " "
|
|
current_func += line.strip()
|
|
else:
|
|
# if is contains "extern", start grabbing
|
|
if "extern" not in line:
|
|
continue
|
|
# Start grabbing the new function
|
|
current_func = line.strip()
|
|
parsing_function = True;
|
|
|
|
# If it contains ';', then the function is complete
|
|
if ";" not in current_func:
|
|
continue
|
|
|
|
# Got function/comment, reset vars
|
|
parsing_function = False;
|
|
func = current_func
|
|
comment = current_comment
|
|
current_func = ""
|
|
current_comment = ""
|
|
|
|
# Discard if it doesn't contain 'SDLCALL'
|
|
if "SDLCALL" not in func:
|
|
if args.debug:
|
|
print(" Discard, doesn't have SDLCALL: " + func)
|
|
continue
|
|
|
|
# Discard if it contains 'SDLMAIN_DECLSPEC' (these are not SDL symbols).
|
|
if "SDLMAIN_DECLSPEC" in func:
|
|
if args.debug:
|
|
print(" Discard, has SDLMAIN_DECLSPEC: " + func)
|
|
continue
|
|
|
|
if args.debug:
|
|
print(" Raw data: " + func);
|
|
|
|
# Replace unusual stuff...
|
|
func = func.replace(" SDL_PRINTF_VARARG_FUNC(1)", "");
|
|
func = func.replace(" SDL_PRINTF_VARARG_FUNC(2)", "");
|
|
func = func.replace(" SDL_PRINTF_VARARG_FUNC(3)", "");
|
|
func = func.replace(" SDL_PRINTF_VARARG_FUNCV(1)", "");
|
|
func = func.replace(" SDL_PRINTF_VARARG_FUNCV(2)", "");
|
|
func = func.replace(" SDL_PRINTF_VARARG_FUNCV(3)", "");
|
|
func = func.replace(" SDL_WPRINTF_VARARG_FUNC(3)", "");
|
|
func = func.replace(" SDL_SCANF_VARARG_FUNC(2)", "");
|
|
func = func.replace(" SDL_SCANF_VARARG_FUNCV(2)", "");
|
|
func = func.replace(" SDL_ANALYZER_NORETURN", "");
|
|
func = func.replace(" SDL_MALLOC", "");
|
|
func = func.replace(" SDL_ALLOC_SIZE2(1, 2)", "");
|
|
func = func.replace(" SDL_ALLOC_SIZE(2)", "");
|
|
func = re.sub(r" SDL_ACQUIRE\(.*\)", "", func);
|
|
func = re.sub(r" SDL_ACQUIRE_SHARED\(.*\)", "", func);
|
|
func = re.sub(r" SDL_TRY_ACQUIRE\(.*\)", "", func);
|
|
func = re.sub(r" SDL_TRY_ACQUIRE_SHARED\(.*\)", "", func);
|
|
func = re.sub(r" SDL_RELEASE\(.*\)", "", func);
|
|
func = re.sub(r" SDL_RELEASE_SHARED\(.*\)", "", func);
|
|
func = re.sub(r" SDL_RELEASE_GENERIC\(.*\)", "", func);
|
|
|
|
# Should be a valid function here
|
|
match = reg_parsing_function.match(func)
|
|
if not match:
|
|
print("Cannot parse: "+ func)
|
|
exit(-1)
|
|
|
|
func_ret = match.group(1)
|
|
func_name = match.group(2)
|
|
func_params = match.group(3)
|
|
|
|
#
|
|
# Parse return value
|
|
#
|
|
func_ret = func_ret.replace('extern', ' ')
|
|
func_ret = func_ret.replace('SDLCALL', ' ')
|
|
func_ret = func_ret.replace('SDL_DECLSPEC', ' ')
|
|
func_ret = func_ret.replace('SDL_DECLSPEC_FREE', ' ')
|
|
# Remove trailing spaces in front of '*'
|
|
tmp = ""
|
|
while func_ret != tmp:
|
|
tmp = func_ret
|
|
func_ret = func_ret.replace(' ', ' ')
|
|
func_ret = func_ret.replace(' *', '*')
|
|
func_ret = func_ret.strip()
|
|
|
|
#
|
|
# Parse parameters
|
|
#
|
|
func_params = func_params.strip()
|
|
if func_params == "":
|
|
func_params = "void"
|
|
|
|
# Identify each function parameters with type and name
|
|
# (eventually there are callbacks of several parameters)
|
|
tmp = func_params.split(',')
|
|
tmp2 = []
|
|
param = ""
|
|
for t in tmp:
|
|
if param == "":
|
|
param = t
|
|
else:
|
|
param = param + "," + t
|
|
# Identify a callback or parameter when there is same count of '(' and ')'
|
|
if param.count('(') == param.count(')'):
|
|
tmp2.append(param.strip())
|
|
param = ""
|
|
|
|
# Process each parameters, separation name and type
|
|
func_param_type = []
|
|
func_param_name = []
|
|
for t in tmp2:
|
|
if t == "void":
|
|
func_param_type.append(t)
|
|
func_param_name.append("")
|
|
continue
|
|
|
|
if t == "...":
|
|
func_param_type.append(t)
|
|
func_param_name.append("")
|
|
continue
|
|
|
|
param_name = ""
|
|
|
|
# parameter is a callback
|
|
if '(' in t:
|
|
match = reg_parsing_callback.match(t)
|
|
if not match:
|
|
print("cannot parse callback: " + t);
|
|
exit(-1)
|
|
a = match.group(1).strip()
|
|
b = match.group(2).strip()
|
|
c = match.group(3).strip()
|
|
|
|
try:
|
|
(param_type, param_name) = b.rsplit('*', 1)
|
|
except:
|
|
param_type = t;
|
|
param_name = "param_name_not_specified"
|
|
|
|
# bug rsplit ??
|
|
if param_name == "":
|
|
param_name = "param_name_not_specified"
|
|
|
|
# reconstruct a callback name for future parsing
|
|
func_param_type.append(a + " (" + param_type.strip() + " *REWRITE_NAME)" + c)
|
|
func_param_name.append(param_name.strip())
|
|
|
|
continue
|
|
|
|
# array like "char *buf[]"
|
|
has_array = False
|
|
if t.endswith("[]"):
|
|
t = t.replace("[]", "")
|
|
has_array = True
|
|
|
|
# pointer
|
|
if '*' in t:
|
|
try:
|
|
(param_type, param_name) = t.rsplit('*', 1)
|
|
except:
|
|
param_type = t;
|
|
param_name = "param_name_not_specified"
|
|
|
|
# bug rsplit ??
|
|
if param_name == "":
|
|
param_name = "param_name_not_specified"
|
|
|
|
val = param_type.strip() + "*REWRITE_NAME"
|
|
|
|
# Remove trailing spaces in front of '*'
|
|
tmp = ""
|
|
while val != tmp:
|
|
tmp = val
|
|
val = val.replace(' ', ' ')
|
|
val = val.replace(' *', '*')
|
|
# first occurrence
|
|
val = val.replace('*', ' *', 1)
|
|
val = val.strip()
|
|
|
|
else: # non pointer
|
|
# cut-off last word on
|
|
try:
|
|
(param_type, param_name) = t.rsplit(' ', 1)
|
|
except:
|
|
param_type = t;
|
|
param_name = "param_name_not_specified"
|
|
|
|
val = param_type.strip() + " REWRITE_NAME"
|
|
|
|
# set back array
|
|
if has_array:
|
|
val += "[]"
|
|
|
|
func_param_type.append(val)
|
|
func_param_name.append(param_name.strip())
|
|
|
|
new_proc = {}
|
|
# Return value type
|
|
new_proc['retval'] = func_ret
|
|
# List of parameters (type + anonymized param name 'REWRITE_NAME')
|
|
new_proc['parameter'] = func_param_type
|
|
# Real parameter name, or 'param_name_not_specified'
|
|
new_proc['parameter_name'] = func_param_name
|
|
# Function name
|
|
new_proc['name'] = func_name
|
|
# Header file
|
|
new_proc['header'] = os.path.basename(filename)
|
|
# Function comment
|
|
new_proc['comment'] = comment
|
|
|
|
full_API.append(new_proc)
|
|
|
|
if args.debug:
|
|
pprint.pprint(new_proc);
|
|
print("\n")
|
|
|
|
if func_name not in existing_procs:
|
|
print("NEW " + func)
|
|
add_dyn_api(new_proc)
|
|
|
|
# For-End line in input
|
|
|
|
input.close()
|
|
# For-End parsing all files of sdl_list_includes
|
|
|
|
# Dump API into a json file
|
|
full_API_json()
|
|
|
|
# Check comment formatting
|
|
check_comment();
|
|
|
|
# Dump API into a json file
|
|
def full_API_json():
|
|
if args.dump:
|
|
filename = 'sdl.json'
|
|
with open(filename, 'w', newline='') as f:
|
|
json.dump(full_API, f, indent=4, sort_keys=True)
|
|
print("dump API to '%s'" % filename);
|
|
|
|
# Check public function comments are correct
|
|
def check_comment_header():
|
|
if not check_comment_header.done:
|
|
check_comment_header.done = True
|
|
print("")
|
|
print("Please fix following warning(s):")
|
|
print("-------------------------------")
|
|
|
|
|
|
def check_comment():
|
|
|
|
check_comment_header.done = False
|
|
|
|
# Check \param
|
|
for i in full_API:
|
|
comment = i['comment']
|
|
name = i['name']
|
|
retval = i['retval']
|
|
header = i['header']
|
|
|
|
expected = len(i['parameter'])
|
|
if expected == 1:
|
|
if i['parameter'][0] == 'void':
|
|
expected = 0;
|
|
count = comment.count("\\param")
|
|
if count != expected:
|
|
# skip SDL_stdinc.h
|
|
if header != 'SDL_stdinc.h':
|
|
# Warning mismatch \param and function prototype
|
|
check_comment_header()
|
|
print(" In file %s: function %s() has %d '\\param' but expected %d" % (header, name, count, expected));
|
|
|
|
# Warning check \param uses the correct parameter name
|
|
# skip SDL_stdinc.h
|
|
if header != 'SDL_stdinc.h':
|
|
parameter_name = i['parameter_name']
|
|
for n in parameter_name:
|
|
if n != "" and "\\param " + n not in comment and "\\param[out] " + n not in comment:
|
|
check_comment_header()
|
|
print(" In file %s: function %s() missing '\\param %s'" % (header, name, n));
|
|
|
|
|
|
# Check \returns
|
|
for i in full_API:
|
|
comment = i['comment']
|
|
name = i['name']
|
|
retval = i['retval']
|
|
header = i['header']
|
|
|
|
expected = 1
|
|
if retval == 'void':
|
|
expected = 0;
|
|
|
|
count = comment.count("\\returns")
|
|
if count != expected:
|
|
# skip SDL_stdinc.h
|
|
if header != 'SDL_stdinc.h':
|
|
# Warning mismatch \param and function prototype
|
|
check_comment_header()
|
|
print(" In file %s: function %s() has %d '\\returns' but expected %d" % (header, name, count, expected));
|
|
|
|
# Check \since
|
|
for i in full_API:
|
|
comment = i['comment']
|
|
name = i['name']
|
|
retval = i['retval']
|
|
header = i['header']
|
|
|
|
expected = 1
|
|
count = comment.count("\\since")
|
|
if count != expected:
|
|
# skip SDL_stdinc.h
|
|
if header != 'SDL_stdinc.h':
|
|
# Warning mismatch \param and function prototype
|
|
check_comment_header()
|
|
print(" In file %s: function %s() has %d '\\since' but expected %d" % (header, name, count, expected));
|
|
|
|
|
|
|
|
# Parse 'sdl_dynapi_procs_h' file to find existing functions
|
|
def find_existing_procs():
|
|
reg = re.compile(r'SDL_DYNAPI_PROC\([^,]*,([^,]*),.*\)')
|
|
ret = []
|
|
input = open(SDL_DYNAPI_PROCS_H)
|
|
|
|
for line in input:
|
|
match = reg.match(line)
|
|
if not match:
|
|
continue
|
|
existing_func = match.group(1)
|
|
ret.append(existing_func);
|
|
# print(existing_func)
|
|
input.close()
|
|
|
|
return ret
|
|
|
|
# Get list of SDL headers
|
|
def get_header_list():
|
|
reg = re.compile(r'^.*\.h$')
|
|
ret = []
|
|
tmp = os.listdir(SDL_INCLUDE_DIR)
|
|
|
|
for f in tmp:
|
|
# Only *.h files
|
|
match = reg.match(f)
|
|
if not match:
|
|
if args.debug:
|
|
print("Skip %s" % f)
|
|
continue
|
|
ret.append(SDL_INCLUDE_DIR / f)
|
|
|
|
return ret
|
|
|
|
# Write the new API in files: _procs.h _overrivides.h and .sym
|
|
def add_dyn_api(proc):
|
|
func_name = proc['name']
|
|
func_ret = proc['retval']
|
|
func_argtype = proc['parameter']
|
|
|
|
|
|
# File: SDL_dynapi_procs.h
|
|
#
|
|
# Add at last
|
|
# SDL_DYNAPI_PROC(SDL_EGLConfig,SDL_EGL_GetCurrentConfig,(void),(),return)
|
|
f = open(SDL_DYNAPI_PROCS_H, "a", newline="")
|
|
dyn_proc = "SDL_DYNAPI_PROC(" + func_ret + "," + func_name + ",("
|
|
|
|
i = ord('a')
|
|
remove_last = False
|
|
for argtype in func_argtype:
|
|
|
|
# Special case, void has no parameter name
|
|
if argtype == "void":
|
|
dyn_proc += "void"
|
|
continue
|
|
|
|
# Var name: a, b, c, ...
|
|
varname = chr(i)
|
|
i += 1
|
|
|
|
tmp = argtype.replace("REWRITE_NAME", varname)
|
|
dyn_proc += tmp + ", "
|
|
remove_last = True
|
|
|
|
# remove last 2 char ', '
|
|
if remove_last:
|
|
dyn_proc = dyn_proc[:-1]
|
|
dyn_proc = dyn_proc[:-1]
|
|
|
|
dyn_proc += "),("
|
|
|
|
i = ord('a')
|
|
remove_last = False
|
|
for argtype in func_argtype:
|
|
|
|
# Special case, void has no parameter name
|
|
if argtype == "void":
|
|
continue
|
|
|
|
# Special case, '...' has no parameter name
|
|
if argtype == "...":
|
|
continue
|
|
|
|
# Var name: a, b, c, ...
|
|
varname = chr(i)
|
|
i += 1
|
|
|
|
dyn_proc += varname + ","
|
|
remove_last = True
|
|
|
|
# remove last char ','
|
|
if remove_last:
|
|
dyn_proc = dyn_proc[:-1]
|
|
|
|
dyn_proc += "),"
|
|
|
|
if func_ret != "void":
|
|
dyn_proc += "return"
|
|
dyn_proc += ")"
|
|
f.write(dyn_proc + "\n")
|
|
f.close()
|
|
|
|
# File: SDL_dynapi_overrides.h
|
|
#
|
|
# Add at last
|
|
# "#define SDL_DelayNS SDL_DelayNS_REAL
|
|
f = open(SDL_DYNAPI_OVERRIDES_H, "a", newline="")
|
|
f.write("#define " + func_name + " " + func_name + "_REAL\n")
|
|
f.close()
|
|
|
|
# File: SDL_dynapi.sym
|
|
#
|
|
# Add before "extra symbols go here" line
|
|
input = open(SDL_DYNAPI_SYM)
|
|
new_input = []
|
|
for line in input:
|
|
if "extra symbols go here" in line:
|
|
new_input.append(" " + func_name + ";\n")
|
|
new_input.append(line)
|
|
input.close()
|
|
f = open(SDL_DYNAPI_SYM, 'w', newline='')
|
|
for line in new_input:
|
|
f.write(line)
|
|
f.close()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--dump', help='output all SDL API into a .json file', action='store_true')
|
|
parser.add_argument('--debug', help='add debug traces', action='store_true')
|
|
args = parser.parse_args()
|
|
|
|
try:
|
|
main()
|
|
except Exception as e:
|
|
print(e)
|
|
exit(-1)
|
|
|
|
print("done!")
|
|
exit(0)
|
|
|