@clypra/runtime 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,173 @@
1
+ /**
2
+ * @clypra/runtime — Resource Validator
3
+ *
4
+ * Validates resource bindings and availability.
5
+ */
6
+
7
+ import type { ResourceValidationResult, ValidationIssue } from "./types";
8
+ import type { FrameGraph, RenderPass } from "../planner/types";
9
+
10
+ /**
11
+ * Resource Validator
12
+ *
13
+ * Validates that all required resources are properly bound.
14
+ */
15
+ export class ResourceValidator {
16
+ /**
17
+ * Validate a frame graph's resources
18
+ */
19
+ validate(frameGraph: FrameGraph): ResourceValidationResult {
20
+ const issues: ValidationIssue[] = [];
21
+ const boundResources = new Set<string>();
22
+ const missingResources: string[] = [];
23
+
24
+ // Collect all allocated resources
25
+ for (const resource of frameGraph.resourceRequests) {
26
+ boundResources.add(resource.id);
27
+ }
28
+
29
+ // Check each pass
30
+ for (const pass of frameGraph.passes) {
31
+ this.validatePass(pass, boundResources, issues, missingResources);
32
+ }
33
+
34
+ return {
35
+ valid: issues.filter((i) => i.type === "error").length === 0,
36
+ issues,
37
+ boundResources: Array.from(boundResources),
38
+ missingResources: Array.from(new Set(missingResources)),
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Validate a single render pass
44
+ */
45
+ private validatePass(pass: RenderPass, boundResources: Set<string>, issues: ValidationIssue[], missingResources: string[]): void {
46
+ // Check input resources
47
+ for (const input of pass.inputs) {
48
+ if (!boundResources.has(input)) {
49
+ issues.push({
50
+ type: "error",
51
+ category: "resource",
52
+ message: `Pass "${pass.id}" references missing input resource: ${input}`,
53
+ details: { passId: pass.id, resourceId: input },
54
+ });
55
+ missingResources.push(input);
56
+ }
57
+ }
58
+
59
+ // Check output resource
60
+ if (!boundResources.has(pass.output)) {
61
+ issues.push({
62
+ type: "error",
63
+ category: "resource",
64
+ message: `Pass "${pass.id}" references missing output resource: ${pass.output}`,
65
+ details: { passId: pass.id, resourceId: pass.output },
66
+ });
67
+ missingResources.push(pass.output);
68
+ }
69
+
70
+ // Validate uniforms
71
+ this.validateUniforms(pass, issues);
72
+
73
+ // Check for common issues
74
+ this.checkPassConfiguration(pass, issues);
75
+ }
76
+
77
+ /**
78
+ * Validate uniforms
79
+ */
80
+ private validateUniforms(pass: RenderPass, issues: ValidationIssue[]): void {
81
+ const uniforms = pass.uniforms;
82
+
83
+ // Check for undefined uniforms
84
+ for (const [key, value] of Object.entries(uniforms)) {
85
+ if (value === undefined || value === null) {
86
+ issues.push({
87
+ type: "warning",
88
+ category: "resource",
89
+ message: `Pass "${pass.id}" has undefined uniform: ${key}`,
90
+ details: { passId: pass.id, uniformName: key },
91
+ });
92
+ }
93
+
94
+ // Check for NaN values
95
+ if (typeof value === "number" && isNaN(value)) {
96
+ issues.push({
97
+ type: "error",
98
+ category: "resource",
99
+ message: `Pass "${pass.id}" has NaN uniform value: ${key}`,
100
+ details: { passId: pass.id, uniformName: key },
101
+ });
102
+ }
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Check pass configuration
108
+ */
109
+ private checkPassConfiguration(pass: RenderPass, issues: ValidationIssue[]): void {
110
+ // Warn about passes with no inputs
111
+ if (pass.inputs.length === 0 && pass.shaderId !== "copy" && pass.shaderId !== "blit") {
112
+ issues.push({
113
+ type: "warning",
114
+ category: "resource",
115
+ message: `Pass "${pass.id}" has no input resources`,
116
+ details: { passId: pass.id },
117
+ });
118
+ }
119
+
120
+ // Warn about empty shader ID
121
+ if (!pass.shaderId || pass.shaderId.trim() === "") {
122
+ issues.push({
123
+ type: "error",
124
+ category: "resource",
125
+ message: `Pass "${pass.id}" has empty shader ID`,
126
+ details: { passId: pass.id },
127
+ });
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Validate resource descriptors
133
+ */
134
+ validateDescriptors(descriptors: Array<{ id: string; width: number; height: number }>): ValidationIssue[] {
135
+ const issues: ValidationIssue[] = [];
136
+ const ids = new Set<string>();
137
+
138
+ for (const descriptor of descriptors) {
139
+ // Check for duplicate IDs
140
+ if (ids.has(descriptor.id)) {
141
+ issues.push({
142
+ type: "error",
143
+ category: "resource",
144
+ message: `Duplicate resource ID: ${descriptor.id}`,
145
+ details: { resourceId: descriptor.id },
146
+ });
147
+ }
148
+ ids.add(descriptor.id);
149
+
150
+ // Check dimensions
151
+ if (descriptor.width <= 0 || descriptor.height <= 0) {
152
+ issues.push({
153
+ type: "error",
154
+ category: "resource",
155
+ message: `Invalid resource dimensions for "${descriptor.id}": ${descriptor.width}x${descriptor.height}`,
156
+ details: { resourceId: descriptor.id, width: descriptor.width, height: descriptor.height },
157
+ });
158
+ }
159
+
160
+ // Warn about very large textures
161
+ if (descriptor.width > 4096 || descriptor.height > 4096) {
162
+ issues.push({
163
+ type: "warning",
164
+ category: "performance",
165
+ message: `Large texture size for "${descriptor.id}": ${descriptor.width}x${descriptor.height}`,
166
+ details: { resourceId: descriptor.id, width: descriptor.width, height: descriptor.height },
167
+ });
168
+ }
169
+ }
170
+
171
+ return issues;
172
+ }
173
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * @clypra/runtime — Shader Validator
3
+ *
4
+ * Validates GLSL shaders for compilation and correctness.
5
+ */
6
+
7
+ import type { ShaderValidationResult, ValidationIssue } from "./types";
8
+
9
+ /**
10
+ * Shader Validator
11
+ *
12
+ * Validates GLSL shaders without requiring a full WebGL context.
13
+ */
14
+ export class ShaderValidator {
15
+ /**
16
+ * Validate a GLSL shader
17
+ */
18
+ validate(source: string, type: "vertex" | "fragment"): ShaderValidationResult {
19
+ const issues: ValidationIssue[] = [];
20
+ const uniformsFound: string[] = [];
21
+ const attributesFound: string[] = [];
22
+
23
+ // Parse uniforms
24
+ const uniformMatches = source.matchAll(/uniform\s+(\w+)\s+(\w+);/g);
25
+ for (const match of uniformMatches) {
26
+ uniformsFound.push(match[2]);
27
+ }
28
+
29
+ // Parse attributes (vertex shaders only)
30
+ if (type === "vertex") {
31
+ const attributeMatches = source.matchAll(/attribute\s+(\w+)\s+(\w+);/g);
32
+ for (const match of attributeMatches) {
33
+ attributesFound.push(match[2]);
34
+ }
35
+ }
36
+
37
+ // Check for common issues
38
+ this.checkSyntax(source, issues);
39
+ this.checkPrecision(source, issues);
40
+ this.checkMainFunction(source, issues);
41
+
42
+ return {
43
+ valid: issues.filter((i) => i.type === "error").length === 0,
44
+ compiled: true, // Will be set by actual compilation
45
+ issues,
46
+ uniformsFound,
47
+ attributesFound,
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Validate shader compilation with WebGL context
53
+ */
54
+ validateWithContext(gl: WebGLRenderingContext | WebGL2RenderingContext, source: string, type: "vertex" | "fragment"): ShaderValidationResult {
55
+ const result = this.validate(source, type);
56
+
57
+ try {
58
+ const shaderType = type === "vertex" ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER;
59
+ const shader = gl.createShader(shaderType);
60
+
61
+ if (!shader) {
62
+ result.issues.push({
63
+ type: "error",
64
+ category: "shader",
65
+ message: "Failed to create shader",
66
+ });
67
+ result.compiled = false;
68
+ result.valid = false;
69
+ return result;
70
+ }
71
+
72
+ gl.shaderSource(shader, source);
73
+ gl.compileShader(shader);
74
+
75
+ const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
76
+ result.compiled = compiled;
77
+
78
+ if (!compiled) {
79
+ const log = gl.getShaderInfoLog(shader);
80
+ result.issues.push({
81
+ type: "error",
82
+ category: "shader",
83
+ message: `Shader compilation failed: ${log}`,
84
+ });
85
+ result.valid = false;
86
+ }
87
+
88
+ gl.deleteShader(shader);
89
+ } catch (error) {
90
+ result.issues.push({
91
+ type: "error",
92
+ category: "shader",
93
+ message: `Shader validation error: ${error}`,
94
+ });
95
+ result.compiled = false;
96
+ result.valid = false;
97
+ }
98
+
99
+ return result;
100
+ }
101
+
102
+ /**
103
+ * Check syntax issues
104
+ */
105
+ private checkSyntax(source: string, issues: ValidationIssue[]): void {
106
+ // Check for missing semicolons
107
+ const lines = source.split("\n");
108
+ lines.forEach((line, index) => {
109
+ const trimmed = line.trim();
110
+
111
+ // Skip empty lines and preprocessor directives
112
+ if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//")) {
113
+ return;
114
+ }
115
+
116
+ // Check for statements that should end with semicolon
117
+ if (!trimmed.endsWith(";") && !trimmed.endsWith("{") && !trimmed.endsWith("}") && trimmed.length > 0) {
118
+ issues.push({
119
+ type: "warning",
120
+ category: "shader",
121
+ message: "Possible missing semicolon",
122
+ location: { line: index + 1 },
123
+ });
124
+ }
125
+ });
126
+ }
127
+
128
+ /**
129
+ * Check precision declarations
130
+ */
131
+ private checkPrecision(source: string, issues: ValidationIssue[]): void {
132
+ // Fragment shaders should have precision declarations
133
+ if (!source.includes("precision")) {
134
+ issues.push({
135
+ type: "warning",
136
+ category: "shader",
137
+ message: "Missing precision qualifier (e.g., precision highp float;)",
138
+ });
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Check for main function
144
+ */
145
+ private checkMainFunction(source: string, issues: ValidationIssue[]): void {
146
+ if (!source.includes("void main()")) {
147
+ issues.push({
148
+ type: "error",
149
+ category: "shader",
150
+ message: "Missing main function",
151
+ });
152
+ }
153
+ }
154
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @clypra/runtime — Validation Types
3
+ */
4
+
5
+ export interface ValidationIssue {
6
+ type: "error" | "warning";
7
+ category: "shader" | "resource" | "graph" | "performance";
8
+ message: string;
9
+ details?: any;
10
+ location?: {
11
+ file?: string;
12
+ line?: number;
13
+ column?: number;
14
+ };
15
+ }
16
+
17
+ export interface ValidationResult {
18
+ valid: boolean;
19
+ issues: ValidationIssue[];
20
+ }
21
+
22
+ export interface ShaderValidationResult extends ValidationResult {
23
+ compiled: boolean;
24
+ uniformsFound: string[];
25
+ attributesFound: string[];
26
+ }
27
+
28
+ export interface ResourceValidationResult extends ValidationResult {
29
+ boundResources: string[];
30
+ missingResources: string[];
31
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "lib": ["ES2022", "DOM"],
6
+ "moduleResolution": "bundler",
7
+ "resolveJsonModule": true,
8
+ "allowJs": true,
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "outDir": "./dist",
17
+ "rootDir": "./src"
18
+ },
19
+ "include": ["src/**/*"],
20
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
21
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: {
5
+ index: "src/index.ts",
6
+ "graph/index": "src/graph/index.ts",
7
+ "planner/index": "src/planner/index.ts",
8
+ "pixi/index": "src/pixi/index.ts",
9
+ "resources/index": "src/resources/index.ts",
10
+ "validation/index": "src/validation/index.ts",
11
+ },
12
+ format: ["esm"],
13
+ dts: true,
14
+ sourcemap: true,
15
+ clean: true,
16
+ splitting: false,
17
+ treeshake: true,
18
+ });