bring first scene graph to live

Tue, 23 Jan 2024 21:34:12 +0100

author
Mike Becker <universe@uap-core.de>
date
Tue, 23 Jan 2024 21:34:12 +0100
changeset 29
1d001eb694dc
parent 28
8acde7d27904
child 30
fceda550ebcb

bring first scene graph to live

src/Makefile file | annotate | diff | comparison | revisions
src/ascension/ascension.h file | annotate | diff | comparison | revisions
src/ascension/scene.h file | annotate | diff | comparison | revisions
src/ascension/text.h file | annotate | diff | comparison | revisions
src/ascension/window.h file | annotate | diff | comparison | revisions
src/scene.c file | annotate | diff | comparison | revisions
src/text.c file | annotate | diff | comparison | revisions
src/window.c file | annotate | diff | comparison | revisions
test/Makefile file | annotate | diff | comparison | revisions
test/sandbox.c file | annotate | diff | comparison | revisions
     1.1 --- a/src/Makefile	Sun Jan 21 14:01:27 2024 +0100
     1.2 +++ b/src/Makefile	Tue Jan 23 21:34:12 2024 +0100
     1.3 @@ -43,14 +43,14 @@
     1.4  
     1.5  $(BUILD_DIR)/context.o: context.c ascension/context.h \
     1.6   ascension/datatypes.h ascension/window.h ascension/primitives.h \
     1.7 - ascension/mesh.h ascension/font.h ascension/error.h ascension/utils.h \
     1.8 - ascension/shader.h
     1.9 + ascension/mesh.h ascension/scene.h ascension/font.h ascension/error.h \
    1.10 + ascension/utils.h ascension/shader.h
    1.11  	@echo "Compiling $<"
    1.12  	$(CC) -o $@ $(CFLAGS) -c $<
    1.13  
    1.14  $(BUILD_DIR)/error.o: error.c ascension/context.h ascension/datatypes.h \
    1.15   ascension/window.h ascension/primitives.h ascension/mesh.h \
    1.16 - ascension/font.h ascension/error.h ascension/utils.h
    1.17 + ascension/scene.h ascension/font.h ascension/error.h ascension/utils.h
    1.18  	@echo "Compiling $<"
    1.19  	$(CC) -o $@ $(CFLAGS) -c $<
    1.20  
    1.21 @@ -60,14 +60,14 @@
    1.22  
    1.23  $(BUILD_DIR)/font.o: font.c ascension/font.h ascension/context.h \
    1.24   ascension/datatypes.h ascension/window.h ascension/primitives.h \
    1.25 - ascension/mesh.h ascension/font.h ascension/error.h
    1.26 + ascension/mesh.h ascension/scene.h ascension/font.h ascension/error.h
    1.27  	@echo "Compiling $<"
    1.28  	$(CC) -o $@ $(CFLAGS) -c $<
    1.29  
    1.30  $(BUILD_DIR)/primitives.o: primitives.c ascension/primitives.h \
    1.31   ascension/mesh.h ascension/error.h ascension/context.h \
    1.32   ascension/datatypes.h ascension/window.h ascension/primitives.h \
    1.33 - ascension/font.h
    1.34 + ascension/scene.h ascension/font.h
    1.35  	@echo "Compiling $<"
    1.36  	$(CC) -o $@ $(CFLAGS) -c $<
    1.37  
    1.38 @@ -81,15 +81,16 @@
    1.39  	$(CC) -o $@ $(CFLAGS) -c $<
    1.40  
    1.41  $(BUILD_DIR)/text.o: text.c ascension/text.h ascension/font.h \
    1.42 - ascension/datatypes.h ascension/context.h ascension/window.h \
    1.43 - ascension/primitives.h ascension/mesh.h ascension/error.h \
    1.44 - ascension/shader.h
    1.45 + ascension/datatypes.h ascension/scene.h ascension/context.h \
    1.46 + ascension/window.h ascension/primitives.h ascension/mesh.h \
    1.47 + ascension/error.h ascension/shader.h
    1.48  	@echo "Compiling $<"
    1.49  	$(CC) -o $@ $(CFLAGS) -c $<
    1.50  
    1.51  $(BUILD_DIR)/window.o: window.c ascension/window.h ascension/datatypes.h \
    1.52 - ascension/primitives.h ascension/mesh.h ascension/context.h \
    1.53 - ascension/window.h ascension/font.h ascension/error.h ascension/utils.h
    1.54 + ascension/primitives.h ascension/mesh.h ascension/scene.h \
    1.55 + ascension/context.h ascension/window.h ascension/font.h \
    1.56 + ascension/error.h ascension/utils.h
    1.57  	@echo "Compiling $<"
    1.58  	$(CC) -o $@ $(CFLAGS) -c $<
    1.59  
     2.1 --- a/src/ascension/ascension.h	Sun Jan 21 14:01:27 2024 +0100
     2.2 +++ b/src/ascension/ascension.h	Tue Jan 23 21:34:12 2024 +0100
     2.3 @@ -32,6 +32,7 @@
     2.4  #include "context.h"
     2.5  #include "shader.h"
     2.6  #include "text.h"
     2.7 +#include "scene.h"
     2.8  
     2.9  #endif /* ASCENSION_H */
    2.10  
     3.1 --- a/src/ascension/scene.h	Sun Jan 21 14:01:27 2024 +0100
     3.2 +++ b/src/ascension/scene.h	Tue Jan 23 21:34:12 2024 +0100
     3.3 @@ -28,29 +28,60 @@
     3.4  #ifndef ASCENSION_SCENE_H
     3.5  #define ASCENSION_SCENE_H
     3.6  
     3.7 -typedef struct AscSceneNode {
     3.8 -    struct AscSceneNode *parent;
     3.9 -    struct AscSceneNode *prev;
    3.10 -    struct AscSceneNode *next;
    3.11 -    struct AscSceneNode *children;
    3.12 -    // TODO: add node contents
    3.13 -} AscSceneNode;
    3.14 +typedef struct AscSceneNode AscSceneNode;
    3.15 +
    3.16 +typedef void(*asc_scene_free_func)(AscSceneNode*);
    3.17 +typedef void(*asc_scene_draw_func)(AscSceneNode const*);
    3.18 +
    3.19 +struct AscSceneNode {
    3.20 +    AscSceneNode *parent;
    3.21 +    AscSceneNode *prev;
    3.22 +    AscSceneNode *next;
    3.23 +    AscSceneNode *children;
    3.24 +    asc_scene_free_func free_func;
    3.25 +    asc_scene_draw_func draw_func;
    3.26 +    // TODO: add more node contents
    3.27 +};
    3.28 +
    3.29 +/**
    3.30 + * Place this as first member of a structure that shall be usable as a scene node.
    3.31 + */
    3.32 +#define extend_asc_scene_node AscSceneNode node
    3.33 +
    3.34 +#define asc_node(obj) (&((obj)->node))
    3.35  
    3.36  typedef struct AscScene {
    3.37      AscSceneNode *root;
    3.38      // TODO: add render groups for batching
    3.39  } AscScene;
    3.40  
    3.41 +__attribute__((__nonnull__))
    3.42  void asc_scene_init(AscScene *scene);
    3.43  
    3.44 +__attribute__((__nonnull__))
    3.45  void asc_scene_destroy(AscScene *scene);
    3.46  
    3.47 -AscSceneNode *asc_scene_node_create(AscSceneNode *parent);
    3.48 +__attribute__((__nonnull__))
    3.49 +void asc_scene_draw(AscScene const *scene);
    3.50 +
    3.51 +__attribute__((__nonnull__))
    3.52 +void asc_scene_add(AscScene *scene, AscSceneNode *node);
    3.53 +
    3.54 +/**
    3.55 + * Creates an empty node that may serve as a container for other nodes.
    3.56 + *
    3.57 + * The free_func of this node will be a simple free().
    3.58 + *
    3.59 + * @return the new node
    3.60 + */
    3.61 +AscSceneNode *asc_scene_node_empty(void);
    3.62  
    3.63  void asc_scene_node_free(AscSceneNode *node);
    3.64  
    3.65 -void asc_scene_node_link(AscSceneNode *node, AscSceneNode *parent);
    3.66 +__attribute__((__nonnull__))
    3.67 +void asc_scene_node_link(AscSceneNode * restrict parent, AscSceneNode * restrict node);
    3.68  
    3.69 +__attribute__((__nonnull__))
    3.70  void asc_scene_node_unlink(AscSceneNode *node);
    3.71  
    3.72  #endif // ASCENSION_SCENE_H
     4.1 --- a/src/ascension/text.h	Sun Jan 21 14:01:27 2024 +0100
     4.2 +++ b/src/ascension/text.h	Tue Jan 23 21:34:12 2024 +0100
     4.3 @@ -30,9 +30,10 @@
     4.4  
     4.5  #include "font.h"
     4.6  #include "datatypes.h"
     4.7 -#include <cx/buffer.h>
     4.8 +#include "scene.h"
     4.9  
    4.10  typedef struct AscText {
    4.11 +    extend_asc_scene_node;
    4.12      char *text;
    4.13      AscFont const *font;
    4.14      asc_vec2i position;
    4.15 @@ -74,13 +75,6 @@
    4.16  void asc_text_update(AscText *node);
    4.17  
    4.18  /**
    4.19 - * Draws the text node in the current frame.
    4.20 - *
    4.21 - * @param node the text node
    4.22 - */
    4.23 -void asc_text_draw(AscText const *node);
    4.24 -
    4.25 -/**
    4.26   * Releases all the memory of this node.
    4.27   *
    4.28   * @param node the text node
     5.1 --- a/src/ascension/window.h	Sun Jan 21 14:01:27 2024 +0100
     5.2 +++ b/src/ascension/window.h	Tue Jan 23 21:34:12 2024 +0100
     5.3 @@ -32,6 +32,7 @@
     5.4  
     5.5  #include "datatypes.h"
     5.6  #include "primitives.h"
     5.7 +#include "scene.h"
     5.8  
     5.9  #ifndef ASC_MAX_WINDOWS
    5.10  /** The maximum number of windows that can exist simultaneously. */
    5.11 @@ -55,6 +56,7 @@
    5.12      AscPrimitives primitives;
    5.13      asc_vec2i dimensions;
    5.14      asc_mat4f projection;
    5.15 +    AscScene ui;
    5.16  } AscWindow;
    5.17  
    5.18  /**
     6.1 --- a/src/scene.c	Sun Jan 21 14:01:27 2024 +0100
     6.2 +++ b/src/scene.c	Tue Jan 23 21:34:12 2024 +0100
     6.3 @@ -28,31 +28,63 @@
     6.4  #include "ascension/scene.h"
     6.5  #include "ascension/error.h"
     6.6  
     6.7 +#include <cx/tree.h>
     6.8 +
     6.9  #include <assert.h>
    6.10  
    6.11 +#define asc_scene_node_layout \
    6.12 +    offsetof(AscSceneNode, parent), offsetof(AscSceneNode, children), \
    6.13 +    offsetof(AscSceneNode, prev), offsetof(AscSceneNode, next)
    6.14 +
    6.15  void asc_scene_init(AscScene *scene) {
    6.16      if (scene->root != NULL) {
    6.17          asc_error("Scene is already initialized.");
    6.18          return;
    6.19      }
    6.20 -    scene->root = asc_scene_node_create(NULL);
    6.21 +    scene->root = asc_scene_node_empty();
    6.22  }
    6.23  
    6.24  void asc_scene_destroy(AscScene *scene) {
    6.25      asc_scene_node_free(scene->root);
    6.26  }
    6.27  
    6.28 -AscSceneNode *asc_scene_node_create(AscSceneNode *parent) {
    6.29 +void asc_scene_add(AscScene *scene, AscSceneNode *node) {
    6.30 +    asc_scene_node_link(scene->root, node);
    6.31 +}
    6.32 +
    6.33 +static void asc_scene_draw_node(AscSceneNode *node) {
    6.34 +    if (node->draw_func != NULL) {
    6.35 +        node->draw_func(node);
    6.36 +    }
    6.37 +    if (node->children != NULL) {
    6.38 +        asc_scene_draw_node(node->children);
    6.39 +    }
    6.40 +    if (node->next != NULL) {
    6.41 +        asc_scene_draw_node(node->next);
    6.42 +    }
    6.43 +}
    6.44 +
    6.45 +void asc_scene_draw(AscScene const *scene) {
    6.46 +    // TODO: replace with UCX tree visitor
    6.47 +    // TODO: don't visit the tree, visit the render groups
    6.48 +    // TODO: avoid recursion
    6.49 +    asc_scene_draw_node(scene->root);
    6.50 +}
    6.51 +
    6.52 +AscSceneNode *asc_scene_node_empty(void) {
    6.53      // TODO: check if this can remain a calloc or if it's too expensive
    6.54      AscSceneNode *node = calloc(1, sizeof(AscSceneNode));
    6.55      assert(node != NULL);
    6.56 -    asc_scene_node_link(node, parent);
    6.57 +    node->free_func = (asc_scene_free_func) free;
    6.58      return node;
    6.59  }
    6.60  
    6.61  void asc_scene_node_free(AscSceneNode *node) {
    6.62      if (node == NULL) return;
    6.63  
    6.64 +    // TODO: replace with UCX tree visitor
    6.65 +    // TODO: avoid recursion
    6.66 +
    6.67      // free the children recursively
    6.68      while (node->children != NULL) {
    6.69          asc_scene_node_free(node->children);
    6.70 @@ -62,36 +94,15 @@
    6.71      asc_scene_node_unlink(node);
    6.72  
    6.73      // free the node
    6.74 -    free(node);
    6.75 -}
    6.76 -
    6.77 -void asc_scene_node_link(
    6.78 -        AscSceneNode *node,
    6.79 -        AscSceneNode *parent
    6.80 -) {
    6.81 -    if (node->parent == parent) return;
    6.82 -    if (node->parent != NULL || parent == NULL) asc_scene_node_unlink(node);
    6.83 -    if (parent != NULL) {
    6.84 -        if (parent->children == NULL) {
    6.85 -            parent->children = node;
    6.86 -        } else {
    6.87 -            parent->children->prev = node;
    6.88 -            node->next = parent->children;
    6.89 -        }
    6.90 -        node->parent = parent;
    6.91 +    if (node->free_func != NULL) {
    6.92 +        node->free_func(node);
    6.93      }
    6.94  }
    6.95  
    6.96 +void asc_scene_node_link(AscSceneNode * restrict parent, AscSceneNode * restrict node) {
    6.97 +    cx_tree_link(parent, node, asc_scene_node_layout);
    6.98 +}
    6.99 +
   6.100  void asc_scene_node_unlink(AscSceneNode *node) {
   6.101 -    if (node->parent == NULL) return;
   6.102 -    AscSceneNode *left = node->prev;
   6.103 -    AscSceneNode *right = node->next;
   6.104 -    assert(left == NULL || node->parent->children != node);
   6.105 -    if (left != NULL) {
   6.106 -        left->next = right;
   6.107 -    } else {
   6.108 -        node->parent->children = right;
   6.109 -    }
   6.110 -    if (right != NULL) right->prev = left;
   6.111 -    node->parent = node->prev = node->next = NULL;
   6.112 +    cx_tree_unlink(node, asc_scene_node_layout);
   6.113  }
   6.114 \ No newline at end of file
     7.1 --- a/src/text.c	Sun Jan 21 14:01:27 2024 +0100
     7.2 +++ b/src/text.c	Tue Jan 23 21:34:12 2024 +0100
     7.3 @@ -32,6 +32,36 @@
     7.4  
     7.5  #include <GL/glew.h>
     7.6  
     7.7 +static void asc_text_draw(AscText const *node) {
     7.8 +    if (node->color.alpha == 0 || node->hidden || node->internal.tex_id == 0) {
     7.9 +        return;
    7.10 +    }
    7.11 +
    7.12 +    glUseProgram(ASC_SHADER_FONT.base.id);
    7.13 +
    7.14 +    // Upload projection
    7.15 +    // TODO: when we group UI draw calls, we don't need this
    7.16 +    glUniformMatrix4fv(ASC_SHADER_FONT.base.projection, 1,
    7.17 +                       GL_FALSE, asc_context.active_window->projection);
    7.18 +
    7.19 +    // Upload model matrix
    7.20 +    asc_mat4f model = {0};
    7.21 +    model[asc_mat4_index(0, 0)] = node->internal.dimension.width;
    7.22 +    model[asc_mat4_index(1, 1)] = node->internal.dimension.height;
    7.23 +    model[asc_mat4_index(3, 0)] = node->position.x;
    7.24 +    model[asc_mat4_index(3, 1)] = node->position.y;
    7.25 +    model[asc_mat4_index(3, 3)] = 1;
    7.26 +    glUniformMatrix4fv(ASC_SHADER_FONT.base.model, 1, GL_FALSE, model);
    7.27 +
    7.28 +    // Upload surface
    7.29 +    glActiveTexture(GL_TEXTURE0);
    7.30 +    glBindTexture(GL_TEXTURE_RECTANGLE, node->internal.tex_id);
    7.31 +    glUniform1i(ASC_SHADER_FONT.surface, 0);
    7.32 +
    7.33 +    // Draw mesh
    7.34 +    asc_primitives_draw_plane();
    7.35 +}
    7.36 +
    7.37  AscText *asc_text(int x, int y, char const *text) {
    7.38      AscText *node = calloc(1, sizeof(AscText));
    7.39      if (node == NULL) {
    7.40 @@ -39,6 +69,9 @@
    7.41          return NULL;
    7.42      }
    7.43  
    7.44 +    node->node.free_func = (asc_scene_free_func) asc_text_free;
    7.45 +    node->node.draw_func = (asc_scene_draw_func) asc_text_draw;
    7.46 +
    7.47      node->position = (asc_vec2i) {x, y};
    7.48      node->font = asc_context.active_font;
    7.49      node->color = asc_context.ink;
    7.50 @@ -93,41 +126,6 @@
    7.51      SDL_FreeSurface(surface);
    7.52  }
    7.53  
    7.54 -void asc_text_draw(AscText const *node) {
    7.55 -    if (node->color.alpha == 0 || node->hidden) {
    7.56 -        return;
    7.57 -    }
    7.58 -
    7.59 -    if (node->internal.tex_id == 0) {
    7.60 -        asc_error("Tried to redraw text node after destruction");
    7.61 -        return;
    7.62 -    }
    7.63 -
    7.64 -    glUseProgram(ASC_SHADER_FONT.base.id);
    7.65 -
    7.66 -    // Upload projection
    7.67 -    // TODO: when we group UI draw calls, we don't need this
    7.68 -    glUniformMatrix4fv(ASC_SHADER_FONT.base.projection, 1,
    7.69 -                       GL_FALSE, asc_context.active_window->projection);
    7.70 -
    7.71 -    // Upload model matrix
    7.72 -    asc_mat4f model = {0};
    7.73 -    model[asc_mat4_index(0, 0)] = node->internal.dimension.width;
    7.74 -    model[asc_mat4_index(1, 1)] = node->internal.dimension.height;
    7.75 -    model[asc_mat4_index(3, 0)] = node->position.x;
    7.76 -    model[asc_mat4_index(3, 1)] = node->position.y;
    7.77 -    model[asc_mat4_index(3, 3)] = 1;
    7.78 -    glUniformMatrix4fv(ASC_SHADER_FONT.base.model, 1, GL_FALSE, model);
    7.79 -
    7.80 -    // Upload surface
    7.81 -    glActiveTexture(GL_TEXTURE0);
    7.82 -    glBindTexture(GL_TEXTURE_RECTANGLE, node->internal.tex_id);
    7.83 -    glUniform1i(ASC_SHADER_FONT.surface, 0);
    7.84 -
    7.85 -    // Draw mesh
    7.86 -    asc_primitives_draw_plane();
    7.87 -}
    7.88 -
    7.89  void asc_text_free(AscText *node) {
    7.90      asc_dprintf("Release text node texture: %u", node->internal.tex_id);
    7.91      glDeleteTextures(1, &node->internal.tex_id);
     8.1 --- a/src/window.c	Sun Jan 21 14:01:27 2024 +0100
     8.2 +++ b/src/window.c	Tue Jan 23 21:34:12 2024 +0100
     8.3 @@ -61,6 +61,10 @@
     8.4      settings->title = "Ascended Window";
     8.5  }
     8.6  
     8.7 +static void asc_window_init_scenes(AscWindow *window) {
     8.8 +    asc_scene_init(&window->ui);
     8.9 +}
    8.10 +
    8.11  AscWindow *asc_window_initialize(unsigned int index, AscWindowSettings const *settings) {
    8.12      if (index >= ASC_MAX_WINDOWS) {
    8.13          asc_error("Maximum number of windows exceeded.");
    8.14 @@ -123,6 +127,7 @@
    8.15  
    8.16              asc_dprintf("Window %u initialized", window->id);
    8.17              if (asc_primitives_init(&window->primitives)) {
    8.18 +                asc_window_init_scenes(window);
    8.19                  asc_context.active_window = window;
    8.20                  return window;
    8.21              } else {
    8.22 @@ -152,6 +157,9 @@
    8.23          asc_context.active_window = NULL;
    8.24      }
    8.25  
    8.26 +    // destroy all scenes
    8.27 +    asc_scene_destroy(&window->ui);
    8.28 +
    8.29      // release context related data (we have to make the GL context current for this)
    8.30      SDL_GL_MakeCurrent(window->window, window->glctx);
    8.31      asc_primitives_destroy(&window->primitives);
    8.32 @@ -180,10 +188,18 @@
    8.33      if (window != active_window) {
    8.34          asc_window_activate(window);
    8.35      }
    8.36 -    SDL_GL_SwapWindow(window->window);
    8.37 +
    8.38 +    // Clear viewport for new frame
    8.39      glViewport(0, 0, window->dimensions.width, window->dimensions.height);
    8.40      glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    8.41      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    8.42 +
    8.43 +    // Draw the UI
    8.44 +    asc_scene_draw(&window->ui);
    8.45 +
    8.46 +    // Swap Buffers
    8.47 +    SDL_GL_SwapWindow(window->window);
    8.48 +
    8.49      if (window != active_window) {
    8.50          asc_window_activate(active_window);
    8.51      }
     9.1 --- a/test/Makefile	Sun Jan 21 14:01:27 2024 +0100
     9.2 +++ b/test/Makefile	Tue Jan 23 21:34:12 2024 +0100
     9.3 @@ -45,8 +45,8 @@
     9.4   ../src/ascension/error.h ../src/ascension/context.h \
     9.5   ../src/ascension/datatypes.h ../src/ascension/window.h \
     9.6   ../src/ascension/primitives.h ../src/ascension/mesh.h \
     9.7 - ../src/ascension/font.h ../src/ascension/shader.h \
     9.8 - ../src/ascension/text.h
     9.9 + ../src/ascension/scene.h ../src/ascension/font.h \
    9.10 + ../src/ascension/shader.h ../src/ascension/text.h
    9.11  	@echo "Compiling $<"
    9.12  	$(CC) -o $@ $(CFLAGS) -c $<
    9.13  
    10.1 --- a/test/sandbox.c	Sun Jan 21 14:01:27 2024 +0100
    10.2 +++ b/test/sandbox.c	Tue Jan 23 21:34:12 2024 +0100
    10.3 @@ -52,17 +52,18 @@
    10.4      AscWindow *window = asc_window_initialize(0, &settings);
    10.5      asc_shader_initialize_predefined();
    10.6  
    10.7 +    // create fps counter and add it to the UI
    10.8      asc_set_font(asc_font(ASC_FONT_REGULAR, 24));
    10.9      asc_ink_rgb(255, 0, 0);
   10.10 -    AscText *fps_counter = asc_text(50, 50, "9999 FPS");
   10.11 +    AscText *fps_counter = asc_text(50, 50, "XXXXX FPS");
   10.12      unsigned last_fps = 0;
   10.13 +    asc_scene_add(&window->ui, asc_node(fps_counter));
   10.14  
   10.15 -    while (asc_loop_next()) {
   10.16 +    do {
   10.17          // quit application on any error
   10.18          if (show_message_box_on_error(window->window)) break;
   10.19  
   10.20 -
   10.21 -        // fps counter
   10.22 +        // update fps counter
   10.23          if (asc_context.elapsed_millis > 0) {
   10.24              unsigned fps = 1000u / asc_context.elapsed_millis;
   10.25              if (fps != last_fps) {
   10.26 @@ -71,11 +72,7 @@
   10.27                  asc_text_update(fps_counter);
   10.28              }
   10.29          }
   10.30 -        asc_text_draw(fps_counter);
   10.31 -    }
   10.32 -
   10.33 -    // TODO: maybe nodes should also be "garbage collected"
   10.34 -    asc_text_free(fps_counter);
   10.35 +    } while (asc_loop_next());
   10.36  
   10.37      asc_context_destroy();
   10.38      return 0;

mercurial